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