001/* 002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020package org.nuxeo.runtime.model.impl; 021 022import java.io.File; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.PrintStream; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.Comparator; 031import java.util.HashMap; 032import java.util.HashSet; 033import java.util.LinkedHashSet; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037import java.util.concurrent.ConcurrentHashMap; 038import java.util.concurrent.ConcurrentMap; 039import java.util.concurrent.ExecutionException; 040import java.util.concurrent.ExecutorService; 041import java.util.concurrent.Executors; 042import java.util.concurrent.Future; 043import java.util.concurrent.TimeUnit; 044import java.util.concurrent.TimeoutException; 045 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.nuxeo.common.Environment; 049import org.nuxeo.common.collections.ListenerList; 050import org.nuxeo.runtime.ComponentEvent; 051import org.nuxeo.runtime.ComponentListener; 052import org.nuxeo.runtime.RuntimeService; 053import org.nuxeo.runtime.api.Framework; 054import org.nuxeo.runtime.model.ComponentInstance; 055import org.nuxeo.runtime.model.ComponentManager; 056import org.nuxeo.runtime.model.ComponentName; 057import org.nuxeo.runtime.model.Extension; 058import org.nuxeo.runtime.model.RegistrationInfo; 059import org.nuxeo.runtime.util.Watch; 060 061/** 062 * @author Bogdan Stefanescu 063 * @author Florent Guillaume 064 */ 065public class ComponentManagerImpl implements ComponentManager { 066 067 private static final Log log = LogFactory.getLog(ComponentManagerImpl.class); 068 069 private static final Log infoLog = LogFactory.getLog(ComponentManager.class); 070 071 // must use an ordered Set to avoid loosing the order of the pending 072 // extensions 073 protected final ConcurrentMap<ComponentName, Set<Extension>> pendingExtensions; 074 075 private ListenerList compListeners; 076 077 /** 078 * Manager listeners. Listen too events like start stop restart etc. 079 * 080 * @since 9.2 081 */ 082 private Listeners listeners; 083 084 private final ConcurrentMap<String, RegistrationInfo> services; 085 086 protected volatile Set<String> blacklist; 087 088 /** 089 * The list of started components (sorted according to the start order). This list is null if the components were 090 * not yet started or were stopped 091 * 092 * @since 9.2 093 */ 094 protected volatile List<RegistrationInfo> started; 095 096 /** 097 * The list of standby components (sorted according to the start order) This list is null if component were not yet 098 * started or not yet put in standby When putting components in standby all started components are stopped and the 099 * {@link #started} list is assigned to {@link #standby} list then the {@link #started} field is nullified. When 100 * resuming standby components the started list is restored from the standby list and the standby field is nullified 101 * 102 * @since 9.2 103 */ 104 protected volatile List<RegistrationInfo> standby; 105 106 /** 107 * A list of registrations that were deployed while the manager was started. 108 * 109 * @since 9.2 110 */ 111 protected volatile Stash stash; 112 113 /** 114 * @since 9.2 115 */ 116 protected volatile ComponentRegistry registry; 117 118 /** 119 * @since 9.2 120 */ 121 protected volatile ComponentRegistry snapshot; 122 123 /** 124 * @since 9.2 125 */ 126 protected volatile boolean isFlushingStash = false; 127 128 /** 129 * @since 9.2 130 */ 131 protected volatile boolean changed = false; 132 133 public ComponentManagerImpl(RuntimeService runtime) { 134 registry = new ComponentRegistry(); 135 pendingExtensions = new ConcurrentHashMap<>(); 136 compListeners = new ListenerList(); 137 listeners = new Listeners(); 138 services = new ConcurrentHashMap<>(); 139 blacklist = new HashSet<>(); 140 stash = new Stash(); 141 } 142 143 /** 144 * @since 9.2 145 */ 146 public final ComponentRegistry getRegistry() { 147 return registry; 148 } 149 150 @Override 151 public Collection<RegistrationInfo> getRegistrations() { 152 return registry.getComponents(); 153 } 154 155 /** 156 * @since 9.2 157 */ 158 @Override 159 public Collection<ComponentName> getResolvedRegistrations() { 160 return registry.getResolvedNames(); 161 } 162 163 @Override 164 public synchronized Map<ComponentName, Set<ComponentName>> getPendingRegistrations() { 165 Map<ComponentName, Set<ComponentName>> pending = new HashMap<>(); 166 for (Map.Entry<ComponentName, Set<ComponentName>> p : registry.getPendingComponents().entrySet()) { 167 pending.put(p.getKey(), new LinkedHashSet<>(p.getValue())); 168 } 169 return pending; 170 } 171 172 @Override 173 public synchronized Map<ComponentName, Set<Extension>> getMissingRegistrations() { 174 Map<ComponentName, Set<Extension>> missing = new HashMap<>(); 175 // also add pending extensions, not resolved because of missing target extension point 176 for (Set<Extension> p : pendingExtensions.values()) { 177 for (Extension e : p) { 178 missing.computeIfAbsent(e.getComponent().getName(), k -> new LinkedHashSet<>()).add(e); 179 } 180 } 181 return missing; 182 } 183 184 /** 185 * Get the needed component names. The returned set is not a copy 186 */ 187 public Set<ComponentName> getNeededRegistrations() { 188 return pendingExtensions.keySet(); 189 } 190 191 /** 192 * Get the pending extensions. The returned set is not a copy 193 */ 194 public Set<Extension> getPendingExtensions(ComponentName name) { 195 return pendingExtensions.get(name); 196 } 197 198 @Override 199 public RegistrationInfo getRegistrationInfo(ComponentName name) { 200 return registry.getComponent(name); 201 } 202 203 @Override 204 public boolean isRegistered(ComponentName name) { 205 return registry.contains(name); 206 } 207 208 @Override 209 public int size() { 210 return registry.size(); 211 } 212 213 @Override 214 public ComponentInstance getComponent(ComponentName name) { 215 RegistrationInfo ri = registry.getComponent(name); 216 return ri != null ? ri.getComponent() : null; 217 } 218 219 @Override 220 public synchronized void shutdown() { 221 stop(); 222 compListeners = null; 223 registry.destroy(); 224 registry = null; 225 snapshot = null; 226 } 227 228 @Override 229 public Set<String> getBlacklist() { 230 return Collections.unmodifiableSet(blacklist); 231 } 232 233 @Override 234 public void setBlacklist(Set<String> blacklist) { 235 this.blacklist = blacklist; 236 } 237 238 @Override 239 public synchronized void register(RegistrationInfo ri) { 240 ComponentName name = ri.getName(); 241 if (blacklist.contains(name.getName())) { 242 log.info("Component " + name.getName() + " was blacklisted. Ignoring."); 243 return; 244 } 245 246 Set<ComponentName> componentsToRemove = stash.toRemove; 247 // Look if the component is not going to be removed when applying the stash 248 // before checking for duplicates. 249 if (!componentsToRemove.contains(name)) { 250 if (registry.contains(name)) { 251 if (name.getName().startsWith("org.nuxeo.runtime.")) { 252 // XXX we hide the fact that nuxeo-runtime bundles are 253 // registered twice 254 // TODO fix the root cause and remove this 255 return; 256 } 257 handleError("Duplicate component name: " + name, null); 258 return; 259 } 260 for (ComponentName n : ri.getAliases()) { 261 if (registry.contains(n)) { 262 handleError("Duplicate component name: " + n + " (alias for " + name + ")", null); 263 return; 264 } 265 } 266 } 267 268 if (shouldStash()) { // stash the registration 269 // should stash before calling ri.attach. 270 stash.add(ri); 271 return; 272 } 273 274 if (hasSnapshot()) { 275 // we are modifying the registry after the snapshot was created 276 changed = true; 277 } 278 279 // TODO it is just about giving manager to RegistrationInfo, do we need that ? 280 if (ri.useFormerLifecycleManagement()) { 281 ((RegistrationInfoImpl) ri).attach(this); 282 } 283 284 try { 285 log.info("Registering component: " + name); 286 if (!registry.addComponent(ri)) { 287 log.info("Registration delayed for component: " + name + ". Waiting for: " 288 + registry.getMissingDependencies(ri.getName())); 289 } 290 } catch (RuntimeException e) { 291 // don't raise this exception, 292 // we want to isolate component errors from other components 293 handleError("Failed to register component: " + name + " (" + e.toString() + ')', e); 294 } 295 } 296 297 @Override 298 public synchronized void unregister(RegistrationInfo regInfo) { 299 unregister(regInfo.getName()); 300 } 301 302 @Override 303 public synchronized void unregister(ComponentName name) { 304 if (shouldStash()) { // stash the un-registration 305 stash.remove(name); 306 return; 307 } 308 if (hasSnapshot()) { 309 changed = true; 310 } 311 try { 312 log.info("Unregistering component: " + name); 313 registry.removeComponent(name); 314 } catch (RuntimeException e) { 315 log.error("Failed to unregister component: " + name, e); 316 } 317 } 318 319 @Override 320 public synchronized boolean unregisterByLocation(String sourceId) { 321 ComponentName name = registry.deployedFiles.remove(sourceId); 322 if (name != null) { 323 unregister(name); 324 return true; 325 } else { 326 return false; 327 } 328 } 329 330 @Override 331 public boolean hasComponentFromLocation(String sourceId) { 332 return registry.deployedFiles.containsKey(sourceId); 333 } 334 335 @Override 336 public void addComponentListener(ComponentListener listener) { 337 compListeners.add(listener); 338 } 339 340 @Override 341 public void removeComponentListener(ComponentListener listener) { 342 compListeners.remove(listener); 343 } 344 345 @Override 346 public void addListener(ComponentManager.Listener listener) { 347 listeners.add(listener); 348 } 349 350 @Override 351 public void removeListener(ComponentManager.Listener listener) { 352 listeners.remove(listener); 353 } 354 355 @Override 356 public ComponentInstance getComponentProvidingService(Class<?> serviceClass) { 357 RegistrationInfo ri = services.get(serviceClass.getName()); 358 if (ri == null) { 359 return null; 360 } 361 ComponentInstance ci = ri.getComponent(); 362 if (ci == null) { 363 if (log.isDebugEnabled()) { 364 log.debug("The component exposing the service " + serviceClass + " is not resolved or not started"); 365 } 366 } 367 return ci; 368 } 369 370 @Override 371 public <T> T getService(Class<T> serviceClass) { 372 ComponentInstance comp = getComponentProvidingService(serviceClass); 373 return comp != null ? comp.getAdapter(serviceClass) : null; 374 } 375 376 @Override 377 public Collection<ComponentName> getActivatingRegistrations() { 378 return getRegistrations(RegistrationInfo.ACTIVATING); 379 } 380 381 @Override 382 public Collection<ComponentName> getStartFailureRegistrations() { 383 return getRegistrations(RegistrationInfo.START_FAILURE); 384 } 385 386 protected Collection<ComponentName> getRegistrations(int state) { 387 RegistrationInfo[] comps = registry.getComponentsArray(); 388 Collection<ComponentName> ret = new ArrayList<>(); 389 for (RegistrationInfo ri : comps) { 390 if (ri.getState() == state) { 391 ret.add(ri.getName()); 392 } 393 } 394 return ret; 395 } 396 397 void sendEvent(ComponentEvent event) { 398 log.debug("Dispatching event: " + event); 399 Object[] listeners = this.compListeners.getListeners(); 400 for (Object listener : listeners) { 401 ((ComponentListener) listener).handleEvent(event); 402 } 403 } 404 405 public synchronized void registerExtension(Extension extension) { 406 ComponentName name = extension.getTargetComponent(); 407 RegistrationInfo ri = registry.getComponent(name); 408 if (ri != null && ri.getComponent() != null) { 409 if (log.isDebugEnabled()) { 410 log.debug("Register contributed extension: " + extension); 411 } 412 loadContributions(ri, extension); 413 ri.getComponent().registerExtension(extension); 414 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_REGISTERED, 415 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 416 } else { 417 // put the extension in the pending queue 418 if (log.isDebugEnabled()) { 419 log.debug("Enqueue contributed extension to pending queue: " + extension); 420 } 421 // must keep order in which extensions are contributed 422 pendingExtensions.computeIfAbsent(name, key -> new LinkedHashSet<>()).add(extension); 423 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_PENDING, 424 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 425 } 426 } 427 428 public synchronized void unregisterExtension(Extension extension) { 429 // TODO check if framework is shutting down and in that case do nothing 430 if (log.isDebugEnabled()) { 431 log.debug("Unregister contributed extension: " + extension); 432 } 433 ComponentName name = extension.getTargetComponent(); 434 RegistrationInfo ri = registry.getComponent(name); 435 if (ri != null) { 436 ComponentInstance co = ri.getComponent(); 437 if (co != null) { 438 co.unregisterExtension(extension); 439 } 440 } else { // maybe it's pending 441 Set<Extension> extensions = pendingExtensions.get(name); 442 if (extensions != null) { 443 extensions.remove(extension); 444 if (extensions.isEmpty()) { 445 pendingExtensions.remove(name); 446 } 447 } 448 } 449 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_UNREGISTERED, 450 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 451 } 452 453 public static void loadContributions(RegistrationInfo ri, Extension xt) { 454 // in new java based system contributions don't need to be loaded, this is a XML specificity reflected by 455 // ExtensionPointImpl coming from XML deserialization 456 if (ri.useFormerLifecycleManagement()) { 457 // Extension point needing to load contribution are ExtensionPointImpl 458 ri.getExtensionPoint(xt.getExtensionPoint()) 459 .filter(xp -> xp.getContributions() != null) 460 .map(ExtensionPointImpl.class::cast) 461 .ifPresent(xp -> { 462 try { 463 Object[] contribs = xp.loadContributions(ri, xt); 464 xt.setContributions(contribs); 465 } catch (RuntimeException e) { 466 handleError("Failed to load contributions for component " + xt.getComponent().getName(), e); 467 } 468 }); 469 } 470 } 471 472 public synchronized void registerServices(RegistrationInfo ri) { 473 String[] serviceNames = ri.getProvidedServiceNames(); 474 if (serviceNames == null) { 475 return; 476 } 477 for (String serviceName : serviceNames) { 478 log.info("Registering service: " + serviceName); 479 services.put(serviceName, ri); 480 // TODO: send notifications 481 } 482 } 483 484 public synchronized void unregisterServices(RegistrationInfo ri) { 485 String[] serviceNames = ri.getProvidedServiceNames(); 486 if (serviceNames == null) { 487 return; 488 } 489 for (String service : serviceNames) { 490 services.remove(service); 491 // TODO: send notifications 492 } 493 } 494 495 @Override 496 public String[] getServices() { 497 return services.keySet().toArray(new String[services.size()]); 498 } 499 500 protected static void handleError(String message, Exception e) { 501 log.error(message, e); 502 Framework.getRuntime().getMessageHandler().addWarning(message); 503 } 504 505 /** 506 * Activate all the resolved components and return the list of activated components in the activation order 507 * 508 * @return the list of the activated components in the activation order 509 * @since 9.2 510 */ 511 protected List<RegistrationInfo> activateComponents() { 512 Watch watch = new Watch(); 513 watch.start(); 514 listeners.beforeActivation(); 515 // make sure we start with a clean pending registry 516 pendingExtensions.clear(); 517 518 List<RegistrationInfo> ris = new ArrayList<>(); 519 // first activate resolved components 520 for (RegistrationInfo ri : registry.getResolvedRegistrationInfo()) { 521 // TODO catch and handle errors 522 watch.start(ri.getName().getName()); 523 activateComponent(ri); 524 ris.add(ri); 525 watch.stop(ri.getName().getName()); 526 } 527 listeners.afterActivation(); 528 watch.stop(); 529 530 if (infoLog.isInfoEnabled()) { 531 infoLog.info("Components activated in " + watch.total.formatSeconds() + " sec."); 532 } 533 writeDevMetrics(watch, "activate"); 534 535 return ris; 536 } 537 538 /** 539 * Activates the given {@link RegistrationInfo}. This step will activate the component, register extensions and then 540 * register services. 541 * 542 * @since 9.3 543 */ 544 protected void activateComponent(RegistrationInfo ri) { 545 if (ri.useFormerLifecycleManagement()) { 546 ((RegistrationInfoImpl) ri).activate(); 547 return; 548 } 549 // TODO should be synchronized on ri ? test without it for now 550 if (ri.getState() != RegistrationInfo.RESOLVED) { 551 return; 552 } 553 ri.setState(RegistrationInfo.ACTIVATING); 554 555 ComponentInstance component = ri.getComponent(); 556 component.activate(); 557 log.info("Component activated: " + ri.getName()); 558 559 // register contributed extensions if any 560 Extension[] extensions = ri.getExtensions(); 561 if (extensions != null) { 562 for (Extension xt : extensions) { 563 xt.setComponent(component); 564 try { 565 registerExtension(xt); 566 } catch (RuntimeException e) { 567 String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " 568 + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName(); 569 log.error(msg, e); 570 msg += " (" + e.toString() + ')'; 571 Framework.getRuntime().getMessageHandler().addError(msg); 572 } 573 } 574 } 575 576 // register pending extensions if any 577 Set<ComponentName> aliases = ri.getAliases(); 578 List<ComponentName> names = new ArrayList<>(1 + aliases.size()); 579 names.add(ri.getName()); 580 names.addAll(aliases); 581 for (ComponentName n : names) { 582 Set<Extension> pendingExt = pendingExtensions.remove(n); 583 if (pendingExt == null) { 584 continue; 585 } 586 for (Extension xt : pendingExt) { 587 try { 588 component.registerExtension(xt); 589 } catch (RuntimeException e) { 590 String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " 591 + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName(); 592 log.error(msg, e); 593 msg += " (" + e.toString() + ')'; 594 Framework.getRuntime().getMessageHandler().addError(msg); 595 } 596 } 597 } 598 599 // register services 600 registerServices(ri); 601 602 ri.setState(RegistrationInfo.ACTIVATED); 603 } 604 605 /** 606 * Deactivate all active components in the reverse resolve order 607 * 608 * @since 9.2 609 */ 610 protected void deactivateComponents(boolean isShutdown) { 611 Watch watch = new Watch(); 612 watch.start(); 613 listeners.beforeDeactivation(); 614 Collection<RegistrationInfo> resolved = registry.getResolvedRegistrationInfo(); 615 List<RegistrationInfo> reverseResolved = new ArrayList<>(resolved); 616 Collections.reverse(reverseResolved); 617 for (RegistrationInfo ri : reverseResolved) { 618 if (ri.isActivated()) { 619 watch.start(ri.getName().getName()); 620 deactivateComponent(ri, isShutdown); 621 watch.stop(ri.getName().getName()); 622 } 623 } 624 // make sure the pending extension map is empty since we didn't unregistered extensions by calling 625 // ri.deactivate(true) 626 pendingExtensions.clear(); 627 listeners.afterDeactivation(); 628 watch.stop(); 629 630 if (infoLog.isInfoEnabled()) { 631 infoLog.info("Components deactivated in " + watch.total.formatSeconds() + " sec."); 632 } 633 writeDevMetrics(watch, "deactivate"); 634 } 635 636 /** 637 * Deactivates the given {@link RegistrationInfo}. This step will unregister the services, unregister the extensions 638 * and then deactivate the component. 639 * 640 * @since 9.3 641 */ 642 protected void deactivateComponent(RegistrationInfo ri, boolean isShutdown) { 643 if (ri.useFormerLifecycleManagement()) { 644 // don't unregister extension if server is shutdown 645 ((RegistrationInfoImpl) ri).deactivate(!isShutdown); 646 return; 647 } 648 int state = ri.getState(); 649 if (state != RegistrationInfo.ACTIVATED && state != RegistrationInfo.START_FAILURE) { 650 return; 651 } 652 653 ri.setState(RegistrationInfo.DEACTIVATING); 654 // TODO no unregisters before, try to do it in new implementation 655 // unregister services 656 unregisterServices(ri); 657 658 // unregister contributed extensions if any 659 Extension[] extensions = ri.getExtensions(); 660 if (extensions != null) { 661 for (Extension xt : extensions) { 662 try { 663 unregisterExtension(xt); 664 } catch (RuntimeException e) { 665 String message = "Failed to unregister extension. Contributor: " + xt.getComponent() + " to " 666 + xt.getTargetComponent() + "; xpoint: " + xt.getExtensionPoint(); 667 log.error(message, e); 668 Framework.getRuntime().getMessageHandler().addError(message); 669 } 670 } 671 } 672 673 ComponentInstance component = ri.getComponent(); 674 component.deactivate(); 675 ri.setState(RegistrationInfo.RESOLVED); 676 } 677 678 /** 679 * Start all given components 680 * 681 * @since 9.2 682 */ 683 protected void startComponents(List<RegistrationInfo> ris, boolean isResume) { 684 Watch watch = new Watch(); 685 watch.start(); 686 listeners.beforeStart(isResume); 687 for (RegistrationInfo ri : ris) { 688 watch.start(ri.getName().getName()); 689 startComponent(ri); 690 watch.stop(ri.getName().getName()); 691 } 692 this.started = ris; 693 listeners.afterStart(isResume); 694 watch.stop(); 695 696 if (infoLog.isInfoEnabled()) { 697 infoLog.info("Components started in " + watch.total.formatSeconds() + " sec."); 698 } 699 writeDevMetrics(watch, "start"); 700 } 701 702 /** 703 * Starts the given {@link RegistrationInfo}. This step will start the component. 704 * 705 * @since 9.3 706 */ 707 protected void startComponent(RegistrationInfo ri) { 708 if (ri.useFormerLifecycleManagement()) { 709 ((RegistrationInfoImpl) ri).start(); 710 return; 711 } 712 if (ri.getState() != RegistrationInfo.ACTIVATED) { 713 return; 714 } 715 try { 716 ri.setState(RegistrationInfo.STARTING); 717 ComponentInstance component = ri.getComponent(); 718 component.start(); 719 ri.setState(RegistrationInfo.STARTED); 720 } catch (RuntimeException e) { 721 log.error(String.format("Component %s notification of application started failed: %s", ri.getName(), 722 e.getMessage()), e); 723 ri.setState(RegistrationInfo.START_FAILURE); 724 } 725 } 726 727 /** 728 * Stop all started components. Stopping components is done in reverse start order. 729 * 730 * @since 9.2 731 */ 732 protected void stopComponents(boolean isStandby) { 733 try { 734 Watch watch = new Watch(); 735 watch.start(); 736 listeners.beforeStop(isStandby); 737 List<RegistrationInfo> list = this.started; 738 for (int i = list.size() - 1; i >= 0; i--) { 739 RegistrationInfo ri = list.get(i); 740 if (ri.isStarted()) { 741 watch.start(ri.getName().getName()); 742 stopComponent(ri); 743 watch.stop(ri.getName().getName()); 744 } 745 } 746 listeners.afterStop(isStandby); 747 watch.stop(); 748 749 if (infoLog.isInfoEnabled()) { 750 infoLog.info("Components stopped in " + watch.total.formatSeconds() + " sec."); 751 } 752 writeDevMetrics(watch, "stop"); 753 } catch (InterruptedException e) { 754 Thread.currentThread().interrupt(); 755 throw new RuntimeException("Interrupted while stopping components", e); 756 } 757 } 758 759 /** 760 * Stops the given {@link RegistrationInfo}. This step will stop the component. 761 * 762 * @since 9.3 763 */ 764 protected void stopComponent(RegistrationInfo ri) throws InterruptedException { 765 if (ri.useFormerLifecycleManagement()) { 766 ((RegistrationInfoImpl) ri).stop(); 767 return; 768 } 769 if (ri.getState() != RegistrationInfo.STARTED) { 770 return; 771 } 772 ri.setState(RegistrationInfo.STOPPING); 773 ComponentInstance component = ri.getComponent(); 774 component.stop(); 775 ri.setState(RegistrationInfo.RESOLVED); 776 } 777 778 @Override 779 public synchronized boolean start() { 780 if (this.started != null) { 781 return false; 782 } 783 784 infoLog.info("Starting Nuxeo Components"); 785 786 List<RegistrationInfo> ris = activateComponents(); 787 788 // TODO we sort using the old start order sorter (see OSGiRuntimeService.RIApplicationStartedComparator) 789 Collections.sort(ris, new RIApplicationStartedComparator()); 790 791 // then start activated components 792 startComponents(ris, false); 793 794 return true; 795 } 796 797 @Override 798 public synchronized boolean stop() { 799 if (this.started == null) { 800 return false; 801 } 802 803 infoLog.info("Stopping Nuxeo Components"); 804 805 try { 806 stopComponents(false); 807 // now deactivate all active components 808 deactivateComponents(true); 809 } finally { 810 this.started = null; 811 } 812 813 return true; 814 } 815 816 @Override 817 public void stop(int timeoutInSeconds) { 818 try { 819 runWihtinTimeout(timeoutInSeconds, TimeUnit.SECONDS, "Timed out on stop, blocking", this::stop); 820 } catch (InterruptedException e) { 821 Thread.currentThread().interrupt(); 822 throw new RuntimeException("Interrupted while stopping components", e); 823 } 824 } 825 826 @Override 827 public synchronized void standby() { 828 if (this.started != null) { 829 try { 830 stopComponents(true); 831 } finally { 832 this.standby = this.started; 833 this.started = null; 834 } 835 } 836 } 837 838 @Override 839 public void standby(int timeoutInSeconds) { 840 try { 841 runWihtinTimeout(timeoutInSeconds, TimeUnit.SECONDS, "Timed out on standby, blocking", this::standby); 842 } catch (InterruptedException e) { 843 Thread.currentThread().interrupt(); 844 throw new RuntimeException("Interrupted while standbying components", e); 845 } 846 } 847 848 @Override 849 public synchronized void resume() { 850 if (this.standby != null) { 851 try { 852 startComponents(this.standby, true); 853 } finally { 854 this.started = this.standby; 855 this.standby = null; 856 } 857 } 858 } 859 860 @Override 861 public boolean isStarted() { 862 return this.started != null; 863 } 864 865 @Override 866 public boolean isStandby() { 867 return this.standby != null; 868 } 869 870 @Override 871 public boolean isRunning() { 872 return this.started != null || this.standby != null; 873 } 874 875 @Override 876 public boolean hasSnapshot() { 877 return this.snapshot != null; 878 } 879 880 @Override 881 public boolean hasChanged() { 882 return this.changed; 883 } 884 885 @Override 886 public synchronized void snapshot() { 887 this.snapshot = new ComponentRegistry(registry); 888 } 889 890 @Override 891 public boolean isStashEmpty() { 892 return stash.isEmpty(); 893 } 894 895 @Override 896 public synchronized void restart(boolean reset) { 897 if (reset) { 898 this.reset(); 899 } else { 900 this.stop(); 901 } 902 this.start(); 903 } 904 905 @Override 906 public synchronized boolean reset() { 907 boolean r = this.stop(); 908 restoreSnapshot(); 909 return r; 910 } 911 912 @Override 913 public synchronized boolean refresh() { 914 return refresh(false); 915 } 916 917 @Override 918 public synchronized boolean refresh(boolean reset) { 919 if (this.stash.isEmpty()) { 920 return false; 921 } 922 boolean requireStart; 923 if (reset) { 924 requireStart = reset(); 925 } else { 926 requireStart = stop(); 927 } 928 Stash currentStash = this.stash; 929 this.stash = new Stash(); 930 applyStash(currentStash); 931 if (requireStart) { 932 start(); 933 } 934 return true; 935 } 936 937 protected synchronized void restoreSnapshot() { 938 if (changed && snapshot != null) { 939 log.info("Restoring components snapshot"); 940 this.registry = new ComponentRegistry(snapshot); 941 changed = false; 942 } 943 } 944 945 /** 946 * Tests whether new registrations should be stashed at registration time. If the component manager was started then 947 * new components should be stashed otherwise they can be registered. 948 * <p /> 949 * TODO: current implementation is stashing after the start completion. Should we also stashing while start is in 950 * progress? 951 */ 952 protected boolean shouldStash() { 953 return isRunning() && !isFlushingStash; 954 } 955 956 protected synchronized void applyStash(Stash stash) { 957 log.info("Applying stashed components"); 958 isFlushingStash = true; 959 try { 960 for (ComponentName name : stash.toRemove) { 961 unregister(name); 962 } 963 for (RegistrationInfo ri : stash.toAdd) { 964 register(ri); 965 } 966 } finally { 967 isFlushingStash = false; 968 } 969 } 970 971 @Override 972 public synchronized void unstash() { 973 Stash currentStash = this.stash; 974 this.stash = new Stash(); 975 976 if (!isRunning()) { 977 applyStash(currentStash); 978 } else { 979 try { 980 applyStashWhenRunning(currentStash); 981 } catch (InterruptedException e) { 982 Thread.currentThread().interrupt(); 983 throw new RuntimeException("Interrupted while unstashing components", e); 984 } 985 } 986 } 987 988 private void applyStashWhenRunning(Stash stash) throws InterruptedException { 989 List<RegistrationInfo> toRemove = stash.getRegistrationsToRemove(registry); 990 if (isStarted()) { 991 for (RegistrationInfo ri : toRemove) { 992 this.started.remove(ri); 993 stopComponent(ri); 994 } 995 } 996 for (RegistrationInfo ri : toRemove) { 997 if (isStandby()) { 998 this.standby.remove(ri); 999 } 1000 deactivateComponent(ri, false); 1001 } 1002 1003 applyStash(stash); 1004 1005 // activate the new components 1006 for (RegistrationInfo ri : stash.toAdd) { 1007 if (ri.isResolved()) { 1008 activateComponent(ri); 1009 } 1010 } 1011 if (isStandby()) { 1012 // activate the new components 1013 for (RegistrationInfo ri : stash.toAdd) { 1014 if (ri.isResolved()) { 1015 activateComponent(ri); 1016 // add new components to standby list 1017 this.standby.add(ri); 1018 } 1019 } 1020 } else if (isStarted()) { 1021 // start the new components and add them to the started list 1022 for (RegistrationInfo ri : stash.toAdd) { 1023 if (ri.isResolved()) { 1024 activateComponent(ri); 1025 } 1026 } 1027 for (RegistrationInfo ri : stash.toAdd) { 1028 if (ri.isActivated()) { 1029 startComponent(ri); 1030 this.started.add(ri); 1031 } 1032 } 1033 } 1034 } 1035 1036 /** 1037 * TODO we use for now the same sorter as OSGIRuntimeService - should be improved later. 1038 */ 1039 protected static class RIApplicationStartedComparator implements Comparator<RegistrationInfo> { 1040 1041 @Override 1042 public int compare(RegistrationInfo r1, RegistrationInfo r2) { 1043 int cmp = Integer.compare(r1.getApplicationStartedOrder(), r2.getApplicationStartedOrder()); 1044 if (cmp == 0) { 1045 // fallback on name order, to be deterministic 1046 cmp = r1.getName().getName().compareTo(r2.getName().getName()); 1047 } 1048 return cmp; 1049 } 1050 1051 } 1052 1053 protected void writeDevMetrics(Watch watch, String type) { 1054 if (!Framework.isDevModeSet()) { 1055 return; 1056 } 1057 File file = new File(Environment.getDefault().getTemp(), type + "-metrics.txt"); 1058 try (PrintStream ps = new PrintStream(new FileOutputStream(file), false, "UTF-8")) { 1059 ps.println(watch.getTotal()); 1060 // print first the longest intervals 1061 Arrays.stream(watch.getIntervals()).sorted(Comparator.reverseOrder()).forEach(ps::println); 1062 ps.flush(); 1063 } catch (IOException e) { 1064 log.error("Failed to write metrics file: " + file, e); 1065 } 1066 } 1067 1068 /** 1069 * Log a warning message if the timeout is reached while executing the given runnable. 1070 */ 1071 protected static void runWihtinTimeout(long timeout, TimeUnit unit, String warn, Runnable runnable) 1072 throws InterruptedException { 1073 ExecutorService executor = Executors.newSingleThreadExecutor(); 1074 try { 1075 Future<?> future = executor.submit(runnable::run); 1076 executor.shutdown(); 1077 try { 1078 try { 1079 future.get(timeout, unit); 1080 } catch (TimeoutException cause) { 1081 log.warn(warn); 1082 future.get(); 1083 } 1084 } catch (ExecutionException cause) { 1085 throw new RuntimeException("Errors caught while stopping components, giving up", cause); 1086 } 1087 } finally { 1088 executor.shutdownNow(); 1089 } 1090 } 1091 1092 protected class Listeners { 1093 1094 protected ListenerList listeners = new ListenerList(); 1095 1096 public void add(ComponentManager.Listener listener) { 1097 listeners.add(listener); 1098 } 1099 1100 public void remove(ComponentManager.Listener listener) { 1101 listeners.remove(listener); 1102 } 1103 1104 public void beforeActivation() { 1105 for (Object listener : listeners.getListeners()) { 1106 ((ComponentManager.Listener) listener).beforeActivation(ComponentManagerImpl.this); 1107 } 1108 } 1109 1110 public void afterActivation() { 1111 for (Object listener : listeners.getListeners()) { 1112 ((ComponentManager.Listener) listener).afterActivation(ComponentManagerImpl.this); 1113 } 1114 } 1115 1116 public void beforeDeactivation() { 1117 for (Object listener : listeners.getListeners()) { 1118 ((ComponentManager.Listener) listener).beforeDeactivation(ComponentManagerImpl.this); 1119 } 1120 } 1121 1122 public void afterDeactivation() { 1123 for (Object listener : listeners.getListeners()) { 1124 ((ComponentManager.Listener) listener).afterDeactivation(ComponentManagerImpl.this); 1125 } 1126 } 1127 1128 public void beforeStart(boolean isResume) { 1129 for (Object listener : listeners.getListeners()) { 1130 ((ComponentManager.Listener) listener).beforeStart(ComponentManagerImpl.this, isResume); 1131 } 1132 } 1133 1134 public void afterStart(boolean isResume) { 1135 for (Object listener : listeners.getListeners()) { 1136 ((ComponentManager.Listener) listener).afterStart(ComponentManagerImpl.this, isResume); 1137 } 1138 } 1139 1140 public void beforeStop(boolean isStandby) { 1141 for (Object listener : listeners.getListeners()) { 1142 ((ComponentManager.Listener) listener).beforeStop(ComponentManagerImpl.this, isStandby); 1143 } 1144 } 1145 1146 public void afterStop(boolean isStandby) { 1147 for (Object listener : listeners.getListeners()) { 1148 ((ComponentManager.Listener) listener).afterStop(ComponentManagerImpl.this, isStandby); 1149 } 1150 } 1151 1152 } 1153 1154 protected static class Stash { 1155 1156 protected volatile List<RegistrationInfo> toAdd; 1157 1158 protected volatile Set<ComponentName> toRemove; 1159 1160 public Stash() { 1161 toAdd = new ArrayList<>(); 1162 toRemove = new HashSet<>(); 1163 } 1164 1165 public void add(RegistrationInfo ri) { 1166 this.toAdd.add(ri); 1167 } 1168 1169 public void remove(ComponentName name) { 1170 this.toRemove.add(name); 1171 } 1172 1173 public boolean isEmpty() { 1174 return toAdd.isEmpty() && toRemove.isEmpty(); 1175 } 1176 1177 public List<RegistrationInfo> getRegistrationsToRemove(ComponentRegistry reg) { 1178 List<RegistrationInfo> ris = new ArrayList<>(); 1179 for (ComponentName name : toRemove) { 1180 RegistrationInfo ri = reg.getComponent(name); 1181 if (ri != null) { 1182 ris.add(ri); 1183 } 1184 } 1185 return ris; 1186 } 1187 1188 } 1189 1190}