001/* 002 * (C) Copyright 2006-2018 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.logging.log4j.LogManager; 047import org.apache.logging.log4j.Logger; 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.DescriptorRegistry; 058import org.nuxeo.runtime.model.Extension; 059import org.nuxeo.runtime.model.RegistrationInfo; 060import org.nuxeo.runtime.util.Watch; 061 062/** 063 * @author Bogdan Stefanescu 064 * @author Florent Guillaume 065 */ 066public class ComponentManagerImpl implements ComponentManager { 067 068 private static final Logger log = LogManager.getLogger(ComponentManagerImpl.class); 069 070 // must use an ordered Set to avoid loosing the order of the pending 071 // extensions 072 protected final ConcurrentMap<ComponentName, Set<Extension>> pendingExtensions; 073 074 private ListenerList compListeners; 075 076 /** 077 * Manager listeners. Listen too events like start stop restart etc. 078 * 079 * @since 9.2 080 */ 081 private Listeners listeners; 082 083 private final ConcurrentMap<String, RegistrationInfo> services; 084 085 protected volatile Set<String> blacklist; 086 087 /** 088 * The list of started components (sorted according to the start order). This list is null if the components were 089 * not yet started or were stopped 090 * 091 * @since 9.2 092 */ 093 protected volatile List<RegistrationInfo> started; 094 095 /** 096 * The list of standby components (sorted according to the start order) This list is null if component were not yet 097 * started or not yet put in standby When putting components in standby all started components are stopped and the 098 * {@link #started} list is assigned to {@link #standby} list then the {@link #started} field is nullified. When 099 * resuming standby components the started list is restored from the standby list and the standby field is nullified 100 * 101 * @since 9.2 102 */ 103 protected volatile List<RegistrationInfo> standby; 104 105 /** 106 * A list of registrations that were deployed while the manager was started. 107 * 108 * @since 9.2 109 */ 110 protected volatile Stash stash; 111 112 /** 113 * @since 9.2 114 */ 115 protected volatile ComponentRegistry registry; 116 117 /** 118 * @since 9.2 119 */ 120 protected volatile ComponentRegistry snapshot; 121 122 /** 123 * @since 10.3 124 */ 125 protected volatile DescriptorRegistry descriptors; 126 127 /** 128 * @since 9.2 129 */ 130 protected volatile boolean isFlushingStash = false; 131 132 /** 133 * @since 9.2 134 */ 135 protected volatile boolean changed = false; 136 137 public ComponentManagerImpl(RuntimeService runtime) { 138 registry = new ComponentRegistry(); 139 pendingExtensions = new ConcurrentHashMap<>(); 140 compListeners = new ListenerList(); 141 listeners = new Listeners(); 142 services = new ConcurrentHashMap<>(); 143 blacklist = new HashSet<>(); 144 stash = new Stash(); 145 descriptors = new DescriptorRegistry(); 146 } 147 148 /** 149 * @since 10.3 150 */ 151 public DescriptorRegistry getDescriptors() { 152 return descriptors; 153 } 154 155 /** 156 * @since 9.2 157 */ 158 public final ComponentRegistry getRegistry() { 159 return registry; 160 } 161 162 @Override 163 public Collection<RegistrationInfo> getRegistrations() { 164 return registry.getComponents(); 165 } 166 167 /** 168 * @since 9.2 169 */ 170 @Override 171 public Collection<ComponentName> getResolvedRegistrations() { 172 return registry.getResolvedNames(); 173 } 174 175 @Override 176 public synchronized Map<ComponentName, Set<ComponentName>> getPendingRegistrations() { 177 Map<ComponentName, Set<ComponentName>> pending = new HashMap<>(); 178 for (Map.Entry<ComponentName, Set<ComponentName>> p : registry.getPendingComponents().entrySet()) { 179 pending.put(p.getKey(), new LinkedHashSet<>(p.getValue())); 180 } 181 return pending; 182 } 183 184 @Override 185 public synchronized Map<ComponentName, Set<Extension>> getMissingRegistrations() { 186 Map<ComponentName, Set<Extension>> missing = new HashMap<>(); 187 // also add pending extensions, not resolved because of missing target extension point 188 for (Set<Extension> p : pendingExtensions.values()) { 189 for (Extension e : p) { 190 missing.computeIfAbsent(e.getComponent().getName(), k -> new LinkedHashSet<>()).add(e); 191 } 192 } 193 return missing; 194 } 195 196 /** 197 * Get the needed component names. The returned set is not a copy 198 */ 199 public Set<ComponentName> getNeededRegistrations() { 200 return pendingExtensions.keySet(); 201 } 202 203 /** 204 * Get the pending extensions. The returned set is not a copy 205 */ 206 public Set<Extension> getPendingExtensions(ComponentName name) { 207 return pendingExtensions.get(name); 208 } 209 210 @Override 211 public RegistrationInfo getRegistrationInfo(ComponentName name) { 212 return registry.getComponent(name); 213 } 214 215 @Override 216 public boolean isRegistered(ComponentName name) { 217 return registry.contains(name); 218 } 219 220 @Override 221 public int size() { 222 return registry.size(); 223 } 224 225 @Override 226 public ComponentInstance getComponent(ComponentName name) { 227 RegistrationInfo ri = registry.getComponent(name); 228 return ri != null ? ri.getComponent() : null; 229 } 230 231 @Override 232 public synchronized void shutdown() { 233 stop(); 234 compListeners = null; 235 registry.destroy(); 236 registry = null; 237 snapshot = null; 238 } 239 240 @Override 241 public Set<String> getBlacklist() { 242 return Collections.unmodifiableSet(blacklist); 243 } 244 245 @Override 246 public void setBlacklist(Set<String> blacklist) { 247 this.blacklist = blacklist; 248 } 249 250 @Override 251 public synchronized void register(RegistrationInfo ri) { 252 ComponentName name = ri.getName(); 253 if (blacklist.contains(name.getName())) { 254 log.debug("Component {} was blacklisted. Ignoring.", name.getName()); 255 return; 256 } 257 258 Set<ComponentName> componentsToRemove = stash.toRemove; 259 // Look if the component is not going to be removed when applying the stash 260 // before checking for duplicates. 261 if (!componentsToRemove.contains(name)) { 262 if (registry.contains(name)) { 263 if (name.getName().startsWith("org.nuxeo.runtime.")) { 264 // XXX we hide the fact that nuxeo-runtime bundles are 265 // registered twice 266 // TODO fix the root cause and remove this 267 return; 268 } 269 handleError("Duplicate component name: " + name, null); 270 return; 271 } 272 for (ComponentName n : ri.getAliases()) { 273 if (registry.contains(n)) { 274 handleError("Duplicate component name: " + n + " (alias for " + name + ")", null); 275 return; 276 } 277 } 278 } 279 280 if (shouldStash()) { // stash the registration 281 // should stash before calling ri.attach. 282 stash.add(ri); 283 return; 284 } 285 286 if (hasSnapshot()) { 287 // we are modifying the registry after the snapshot was created 288 changed = true; 289 } 290 291 // TODO it is just about giving manager to RegistrationInfo, do we need that ? 292 if (ri.useFormerLifecycleManagement()) { 293 ((RegistrationInfoImpl) ri).attach(this); 294 } 295 296 try { 297 log.debug("Registering component: {}", name); 298 if (!registry.addComponent(ri)) { 299 log.info("Registration delayed for component: " + name + ". Waiting for: " 300 + registry.getMissingDependencies(ri.getName())); 301 } 302 } catch (RuntimeException e) { 303 // don't raise this exception, 304 // we want to isolate component errors from other components 305 handleError("Failed to register component: " + name + " (" + e.toString() + ')', e); 306 } 307 } 308 309 @Override 310 public synchronized void unregister(RegistrationInfo regInfo) { 311 unregister(regInfo.getName()); 312 } 313 314 @Override 315 public synchronized void unregister(ComponentName name) { 316 if (shouldStash()) { // stash the un-registration 317 stash.remove(name); 318 return; 319 } 320 if (hasSnapshot()) { 321 changed = true; 322 } 323 try { 324 log.debug("Unregistering component: {}", name); 325 registry.removeComponent(name); 326 } catch (RuntimeException e) { 327 log.error("Failed to unregister component: {}", name, e); 328 } 329 } 330 331 @Override 332 public synchronized boolean unregisterByLocation(String sourceId) { 333 ComponentName name = registry.deployedFiles.remove(sourceId); 334 if (name != null) { 335 unregister(name); 336 return true; 337 } else { 338 return false; 339 } 340 } 341 342 @Override 343 public boolean hasComponentFromLocation(String sourceId) { 344 return registry.deployedFiles.containsKey(sourceId); 345 } 346 347 @Override 348 public void addComponentListener(ComponentListener listener) { 349 compListeners.add(listener); 350 } 351 352 @Override 353 public void removeComponentListener(ComponentListener listener) { 354 compListeners.remove(listener); 355 } 356 357 @Override 358 public void addListener(ComponentManager.Listener listener) { 359 listeners.add(listener); 360 } 361 362 @Override 363 public void removeListener(ComponentManager.Listener listener) { 364 listeners.remove(listener); 365 } 366 367 @Override 368 public ComponentInstance getComponentProvidingService(Class<?> serviceClass) { 369 RegistrationInfo ri = services.get(serviceClass.getName()); 370 if (ri == null) { 371 return null; 372 } 373 ComponentInstance ci = ri.getComponent(); 374 if (ci == null) { 375 log.debug("The component exposing the service {} is not resolved or not started", serviceClass); 376 } 377 return ci; 378 } 379 380 @Override 381 public <T> T getService(Class<T> serviceClass) { 382 ComponentInstance comp = getComponentProvidingService(serviceClass); 383 return comp != null ? comp.getAdapter(serviceClass) : null; 384 } 385 386 @Override 387 public Collection<ComponentName> getActivatingRegistrations() { 388 return getRegistrations(RegistrationInfo.ACTIVATING); 389 } 390 391 @Override 392 public Collection<ComponentName> getStartFailureRegistrations() { 393 return getRegistrations(RegistrationInfo.START_FAILURE); 394 } 395 396 protected Collection<ComponentName> getRegistrations(int state) { 397 RegistrationInfo[] comps = registry.getComponentsArray(); 398 Collection<ComponentName> ret = new ArrayList<>(); 399 for (RegistrationInfo ri : comps) { 400 if (ri.getState() == state) { 401 ret.add(ri.getName()); 402 } 403 } 404 return ret; 405 } 406 407 void sendEvent(ComponentEvent event) { 408 log.trace("Dispatching event: {}", event); 409 Object[] listeners = this.compListeners.getListeners(); 410 for (Object listener : listeners) { 411 ((ComponentListener) listener).handleEvent(event); 412 } 413 } 414 415 public synchronized void registerExtension(Extension extension) { 416 ComponentName name = extension.getTargetComponent(); 417 RegistrationInfo ri = registry.getComponent(name); 418 if (ri != null && ri.getComponent() != null) { 419 log.debug("Register contributed extension: {}", extension); 420 loadContributions(ri, extension); 421 ri.getComponent().registerExtension(extension); 422 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_REGISTERED, 423 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 424 } else { 425 // put the extension in the pending queue 426 log.debug("Enqueue contributed extension to pending queue: {}", extension); 427 // must keep order in which extensions are contributed 428 pendingExtensions.computeIfAbsent(name, key -> new LinkedHashSet<>()).add(extension); 429 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_PENDING, 430 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 431 } 432 } 433 434 public synchronized void unregisterExtension(Extension extension) { 435 // TODO check if framework is shutting down and in that case do nothing 436 log.debug("Unregister contributed extension: {}", extension); 437 ComponentName name = extension.getTargetComponent(); 438 RegistrationInfo ri = registry.getComponent(name); 439 if (ri != null) { 440 ComponentInstance co = ri.getComponent(); 441 if (co != null) { 442 co.unregisterExtension(extension); 443 } 444 } else { // maybe it's pending 445 Set<Extension> extensions = pendingExtensions.get(name); 446 if (extensions != null) { 447 extensions.remove(extension); 448 if (extensions.isEmpty()) { 449 pendingExtensions.remove(name); 450 } 451 } 452 } 453 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_UNREGISTERED, 454 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 455 } 456 457 public static void loadContributions(RegistrationInfo ri, Extension xt) { 458 // in new java based system contributions don't need to be loaded, this is a XML specificity reflected by 459 // ExtensionPointImpl coming from XML deserialization 460 if (ri.useFormerLifecycleManagement()) { 461 // Extension point needing to load contribution are ExtensionPointImpl 462 ri.getExtensionPoint(xt.getExtensionPoint()) 463 .filter(xp -> xp.getContributions() != null) 464 .map(ExtensionPointImpl.class::cast) 465 .ifPresent(xp -> { 466 try { 467 Object[] contribs = xp.loadContributions(ri, xt); 468 xt.setContributions(contribs); 469 } catch (RuntimeException e) { 470 handleError("Failed to load contributions for component " + xt.getComponent().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, Exception e) { 503 log.error(message, e); 504 Framework.getRuntime().getMessageHandler().addWarning(message); 505 } 506 507 /** 508 * Activate all the resolved components and return the list of activated components in the activation order 509 * 510 * @return the list of the activated components in the activation order 511 * @since 9.2 512 */ 513 protected List<RegistrationInfo> activateComponents() { 514 log.info("Activate components"); 515 Watch watch = new Watch(); 516 watch.start(); 517 listeners.beforeActivation(); 518 // make sure we start with a clean pending registry 519 pendingExtensions.clear(); 520 521 List<RegistrationInfo> ris = new ArrayList<>(); 522 // first activate resolved components 523 for (RegistrationInfo ri : registry.getResolvedRegistrationInfo()) { 524 // TODO catch and handle errors 525 watch.start(ri.getName().getName()); 526 activateComponent(ri); 527 ris.add(ri); 528 watch.stop(ri.getName().getName()); 529 } 530 listeners.afterActivation(); 531 watch.stop(); 532 533 log.debug("Components activated in {}s", watch.total::formatSeconds); 534 writeDevMetrics(watch, "activate"); 535 536 return ris; 537 } 538 539 /** 540 * Activates the given {@link RegistrationInfo}. This step will activate the component, register extensions and then 541 * register services. 542 * 543 * @since 9.3 544 */ 545 protected void activateComponent(RegistrationInfo ri) { 546 if (ri.useFormerLifecycleManagement()) { 547 ((RegistrationInfoImpl) ri).activate(); 548 return; 549 } 550 // TODO should be synchronized on ri ? test without it for now 551 if (ri.getState() != RegistrationInfo.RESOLVED) { 552 return; 553 } 554 ri.setState(RegistrationInfo.ACTIVATING); 555 556 ComponentInstance component = ri.getComponent(); 557 component.activate(); 558 log.debug("Component activated: {}", ri.getName()); 559 560 // register contributed extensions if any 561 Extension[] extensions = ri.getExtensions(); 562 if (extensions != null) { 563 for (Extension xt : extensions) { 564 xt.setComponent(component); 565 try { 566 registerExtension(xt); 567 } catch (RuntimeException e) { 568 String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " 569 + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName(); 570 log.error(msg, e); 571 msg += " (" + e.toString() + ')'; 572 Framework.getRuntime().getMessageHandler().addError(msg); 573 } 574 } 575 } 576 577 // register pending extensions if any 578 Set<ComponentName> aliases = ri.getAliases(); 579 List<ComponentName> names = new ArrayList<>(1 + aliases.size()); 580 names.add(ri.getName()); 581 names.addAll(aliases); 582 for (ComponentName n : names) { 583 Set<Extension> pendingExt = pendingExtensions.remove(n); 584 if (pendingExt == null) { 585 continue; 586 } 587 for (Extension xt : pendingExt) { 588 try { 589 component.registerExtension(xt); 590 } catch (RuntimeException e) { 591 String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " 592 + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName(); 593 log.error(msg, e); 594 msg += " (" + e.toString() + ')'; 595 Framework.getRuntime().getMessageHandler().addError(msg); 596 } 597 } 598 } 599 600 // register services 601 registerServices(ri); 602 603 ri.setState(RegistrationInfo.ACTIVATED); 604 } 605 606 /** 607 * Deactivate all active components in the reverse resolve order 608 * 609 * @since 9.2 610 */ 611 protected void deactivateComponents(boolean isShutdown) { 612 log.info("Deactivate components"); 613 Watch watch = new Watch(); 614 watch.start(); 615 listeners.beforeDeactivation(); 616 Collection<RegistrationInfo> resolved = registry.getResolvedRegistrationInfo(); 617 List<RegistrationInfo> reverseResolved = new ArrayList<>(resolved); 618 Collections.reverse(reverseResolved); 619 for (RegistrationInfo ri : reverseResolved) { 620 if (ri.isActivated()) { 621 watch.start(ri.getName().getName()); 622 deactivateComponent(ri, isShutdown); 623 watch.stop(ri.getName().getName()); 624 } 625 } 626 // make sure the pending extension map is empty since we didn't unregistered extensions by calling 627 // ri.deactivate(true) 628 pendingExtensions.clear(); 629 listeners.afterDeactivation(); 630 watch.stop(); 631 632 log.debug("Components deactivated in {}s", watch.total::formatSeconds); 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 log.debug("Component deactivated: {}", ri.getName()); 676 ri.setState(RegistrationInfo.RESOLVED); 677 } 678 679 /** 680 * Start all given components 681 * 682 * @since 9.2 683 */ 684 protected void startComponents(List<RegistrationInfo> ris, boolean isResume) { 685 log.info("Start components (isResume={})", isResume); 686 Watch watch = new Watch(); 687 watch.start(); 688 listeners.beforeStart(isResume); 689 for (RegistrationInfo ri : ris) { 690 watch.start(ri.getName().getName()); 691 startComponent(ri); 692 watch.stop(ri.getName().getName()); 693 } 694 this.started = ris; 695 listeners.afterStart(isResume); 696 watch.stop(); 697 698 log.debug("Components started in {}s", watch.total::formatSeconds); 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 log.debug("Component started: {}", ri.getName()); 720 ri.setState(RegistrationInfo.STARTED); 721 } catch (RuntimeException e) { 722 log.error("Component {} notification of application started failed: {}", ri.getName(), 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 log.info("Stop components (isStandby={})", isStandby); 734 try { 735 Watch watch = new Watch(); 736 watch.start(); 737 listeners.beforeStop(isStandby); 738 List<RegistrationInfo> list = this.started; 739 for (int i = list.size() - 1; i >= 0; i--) { 740 RegistrationInfo ri = list.get(i); 741 if (ri.isStarted()) { 742 watch.start(ri.getName().getName()); 743 stopComponent(ri); 744 watch.stop(ri.getName().getName()); 745 } 746 } 747 listeners.afterStop(isStandby); 748 watch.stop(); 749 750 log.debug("Components stopped in {}s", watch.total::formatSeconds); 751 writeDevMetrics(watch, "stop"); 752 } catch (InterruptedException e) { 753 Thread.currentThread().interrupt(); 754 throw new RuntimeException("Interrupted while stopping components", e); 755 } 756 } 757 758 /** 759 * Stops the given {@link RegistrationInfo}. This step will stop the component. 760 * 761 * @since 9.3 762 */ 763 protected void stopComponent(RegistrationInfo ri) throws InterruptedException { 764 if (ri.useFormerLifecycleManagement()) { 765 ((RegistrationInfoImpl) ri).stop(); 766 return; 767 } 768 if (ri.getState() != RegistrationInfo.STARTED) { 769 return; 770 } 771 ri.setState(RegistrationInfo.STOPPING); 772 ComponentInstance component = ri.getComponent(); 773 component.stop(); 774 log.debug("Component stopped: {}", ri.getName()); 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 log.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 ris.sort(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 log.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.debug("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}