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 // FIXME: extensions is a set of Extensions, not ComponentNames. 444 extensions.remove(name); 445 if (extensions.isEmpty()) { 446 pendingExtensions.remove(name); 447 } 448 } 449 } 450 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_UNREGISTERED, 451 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 452 } 453 454 public static void loadContributions(RegistrationInfo ri, Extension xt) { 455 // in new java based system contributions don't need to be loaded, this is a XML specificity reflected by 456 // ExtensionPointImpl coming from XML deserialization 457 if (ri.useFormerLifecycleManagement()) { 458 // Extension point needing to load contribution are ExtensionPointImpl 459 ri.getExtensionPoint(xt.getExtensionPoint()) 460 .filter(xp -> xp.getContributions() != null) 461 .map(ExtensionPointImpl.class::cast) 462 .ifPresent(xp -> { 463 try { 464 Object[] contribs = xp.loadContributions(ri, xt); 465 xt.setContributions(contribs); 466 } catch (RuntimeException e) { 467 handleError("Failed to load contributions for component " + xt.getComponent().getName(), e); 468 } 469 }); 470 } 471 } 472 473 public synchronized void registerServices(RegistrationInfo ri) { 474 String[] serviceNames = ri.getProvidedServiceNames(); 475 if (serviceNames == null) { 476 return; 477 } 478 for (String serviceName : serviceNames) { 479 log.info("Registering service: " + serviceName); 480 services.put(serviceName, ri); 481 // TODO: send notifications 482 } 483 } 484 485 public synchronized void unregisterServices(RegistrationInfo ri) { 486 String[] serviceNames = ri.getProvidedServiceNames(); 487 if (serviceNames == null) { 488 return; 489 } 490 for (String service : serviceNames) { 491 services.remove(service); 492 // TODO: send notifications 493 } 494 } 495 496 @Override 497 public String[] getServices() { 498 return services.keySet().toArray(new String[services.size()]); 499 } 500 501 protected static void handleError(String message, Exception e) { 502 log.error(message, e); 503 Framework.getRuntime().getMessageHandler().addWarning(message); 504 } 505 506 /** 507 * Activate all the resolved components and return the list of activated components in the activation order 508 * 509 * @return the list of the activated components in the activation order 510 * @since 9.2 511 */ 512 protected List<RegistrationInfo> activateComponents() { 513 Watch watch = new Watch(); 514 watch.start(); 515 listeners.beforeActivation(); 516 // make sure we start with a clean pending registry 517 pendingExtensions.clear(); 518 519 List<RegistrationInfo> ris = new ArrayList<>(); 520 // first activate resolved components 521 for (RegistrationInfo ri : registry.getResolvedRegistrationInfo()) { 522 // TODO catch and handle errors 523 watch.start(ri.getName().getName()); 524 activateComponent(ri); 525 ris.add(ri); 526 watch.stop(ri.getName().getName()); 527 } 528 listeners.afterActivation(); 529 watch.stop(); 530 531 if (infoLog.isInfoEnabled()) { 532 infoLog.info("Components activated in " + watch.total.formatSeconds() + " sec."); 533 } 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.info("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 Watch watch = new Watch(); 613 watch.start(); 614 listeners.beforeDeactivation(); 615 Collection<RegistrationInfo> resolved = registry.getResolvedRegistrationInfo(); 616 List<RegistrationInfo> reverseResolved = new ArrayList<>(resolved); 617 Collections.reverse(reverseResolved); 618 for (RegistrationInfo ri : reverseResolved) { 619 if (ri.isActivated()) { 620 watch.start(ri.getName().getName()); 621 deactivateComponent(ri, isShutdown); 622 watch.stop(ri.getName().getName()); 623 } 624 } 625 // make sure the pending extension map is empty since we didn't unregistered extensions by calling 626 // ri.deactivate(true) 627 pendingExtensions.clear(); 628 listeners.afterDeactivation(); 629 watch.stop(); 630 631 if (infoLog.isInfoEnabled()) { 632 infoLog.info("Components deactivated in " + watch.total.formatSeconds() + " sec."); 633 } 634 writeDevMetrics(watch, "deactivate"); 635 } 636 637 /** 638 * Deactivates the given {@link RegistrationInfo}. This step will unregister the services, unregister the extensions 639 * and then deactivate the component. 640 * 641 * @since 9.3 642 */ 643 protected void deactivateComponent(RegistrationInfo ri, boolean isShutdown) { 644 if (ri.useFormerLifecycleManagement()) { 645 // don't unregister extension if server is shutdown 646 ((RegistrationInfoImpl) ri).deactivate(!isShutdown); 647 return; 648 } 649 int state = ri.getState(); 650 if (state != RegistrationInfo.ACTIVATED && state != RegistrationInfo.START_FAILURE) { 651 return; 652 } 653 654 ri.setState(RegistrationInfo.DEACTIVATING); 655 // TODO no unregisters before, try to do it in new implementation 656 // unregister services 657 unregisterServices(ri); 658 659 // unregister contributed extensions if any 660 Extension[] extensions = ri.getExtensions(); 661 if (extensions != null) { 662 for (Extension xt : extensions) { 663 try { 664 unregisterExtension(xt); 665 } catch (RuntimeException e) { 666 String message = "Failed to unregister extension. Contributor: " + xt.getComponent() + " to " 667 + xt.getTargetComponent() + "; xpoint: " + xt.getExtensionPoint(); 668 log.error(message, e); 669 Framework.getRuntime().getMessageHandler().addError(message); 670 } 671 } 672 } 673 674 ComponentInstance component = ri.getComponent(); 675 component.deactivate(); 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 Watch watch = new Watch(); 686 watch.start(); 687 listeners.beforeStart(isResume); 688 for (RegistrationInfo ri : ris) { 689 watch.start(ri.getName().getName()); 690 startComponent(ri); 691 watch.stop(ri.getName().getName()); 692 } 693 this.started = ris; 694 listeners.afterStart(isResume); 695 watch.stop(); 696 697 if (infoLog.isInfoEnabled()) { 698 infoLog.info("Components started in " + watch.total.formatSeconds() + " sec."); 699 } 700 writeDevMetrics(watch, "start"); 701 } 702 703 /** 704 * Starts the given {@link RegistrationInfo}. This step will start the component. 705 * 706 * @since 9.3 707 */ 708 protected void startComponent(RegistrationInfo ri) { 709 if (ri.useFormerLifecycleManagement()) { 710 ((RegistrationInfoImpl) ri).start(); 711 return; 712 } 713 if (ri.getState() != RegistrationInfo.ACTIVATED) { 714 return; 715 } 716 try { 717 ri.setState(RegistrationInfo.STARTING); 718 ComponentInstance component = ri.getComponent(); 719 component.start(); 720 ri.setState(RegistrationInfo.STARTED); 721 } catch (RuntimeException e) { 722 log.error(String.format("Component %s notification of application started failed: %s", ri.getName(), 723 e.getMessage()), e); 724 ri.setState(RegistrationInfo.START_FAILURE); 725 } 726 } 727 728 /** 729 * Stop all started components. Stopping components is done in reverse start order. 730 * 731 * @since 9.2 732 */ 733 protected void stopComponents(boolean 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 if (infoLog.isInfoEnabled()) { 751 infoLog.info("Components stopped in " + watch.total.formatSeconds() + " sec."); 752 } 753 writeDevMetrics(watch, "stop"); 754 } catch (InterruptedException e) { 755 Thread.currentThread().interrupt(); 756 throw new RuntimeException("Interrupted while stopping components", e); 757 } 758 } 759 760 /** 761 * Stops the given {@link RegistrationInfo}. This step will stop the component. 762 * 763 * @since 9.3 764 */ 765 protected void stopComponent(RegistrationInfo ri) throws InterruptedException { 766 if (ri.useFormerLifecycleManagement()) { 767 ((RegistrationInfoImpl) ri).stop(); 768 return; 769 } 770 if (ri.getState() != RegistrationInfo.STARTED) { 771 return; 772 } 773 ri.setState(RegistrationInfo.STOPPING); 774 ComponentInstance component = ri.getComponent(); 775 component.stop(); 776 ri.setState(RegistrationInfo.RESOLVED); 777 } 778 779 @Override 780 public synchronized boolean start() { 781 if (this.started != null) { 782 return false; 783 } 784 785 infoLog.info("Starting Nuxeo Components"); 786 787 List<RegistrationInfo> ris = activateComponents(); 788 789 // TODO we sort using the old start order sorter (see OSGiRuntimeService.RIApplicationStartedComparator) 790 Collections.sort(ris, new RIApplicationStartedComparator()); 791 792 // then start activated components 793 startComponents(ris, false); 794 795 return true; 796 } 797 798 @Override 799 public synchronized boolean stop() { 800 if (this.started == null) { 801 return false; 802 } 803 804 infoLog.info("Stopping Nuxeo Components"); 805 806 try { 807 stopComponents(false); 808 // now deactivate all active components 809 deactivateComponents(true); 810 } finally { 811 this.started = null; 812 } 813 814 return true; 815 } 816 817 @Override 818 public void stop(int timeoutInSeconds) { 819 try { 820 runWihtinTimeout(timeoutInSeconds, TimeUnit.SECONDS, "Timed out on stop, blocking", this::stop); 821 } catch (InterruptedException e) { 822 Thread.currentThread().interrupt(); 823 throw new RuntimeException("Interrupted while stopping components", e); 824 } 825 } 826 827 @Override 828 public synchronized void standby() { 829 if (this.started != null) { 830 try { 831 stopComponents(true); 832 } finally { 833 this.standby = this.started; 834 this.started = null; 835 } 836 } 837 } 838 839 @Override 840 public void standby(int timeoutInSeconds) { 841 try { 842 runWihtinTimeout(timeoutInSeconds, TimeUnit.SECONDS, "Timed out on standby, blocking", this::standby); 843 } catch (InterruptedException e) { 844 Thread.currentThread().interrupt(); 845 throw new RuntimeException("Interrupted while standbying components", e); 846 } 847 } 848 849 @Override 850 public synchronized void resume() { 851 if (this.standby != null) { 852 try { 853 startComponents(this.standby, true); 854 } finally { 855 this.started = this.standby; 856 this.standby = null; 857 } 858 } 859 } 860 861 @Override 862 public boolean isStarted() { 863 return this.started != null; 864 } 865 866 @Override 867 public boolean isStandby() { 868 return this.standby != null; 869 } 870 871 @Override 872 public boolean isRunning() { 873 return this.started != null || this.standby != null; 874 } 875 876 @Override 877 public boolean hasSnapshot() { 878 return this.snapshot != null; 879 } 880 881 @Override 882 public boolean hasChanged() { 883 return this.changed; 884 } 885 886 @Override 887 public synchronized void snapshot() { 888 this.snapshot = new ComponentRegistry(registry); 889 } 890 891 @Override 892 public boolean isStashEmpty() { 893 return stash.isEmpty(); 894 } 895 896 @Override 897 public synchronized void restart(boolean reset) { 898 if (reset) { 899 this.reset(); 900 } else { 901 this.stop(); 902 } 903 this.start(); 904 } 905 906 @Override 907 public synchronized boolean reset() { 908 boolean r = this.stop(); 909 restoreSnapshot(); 910 return r; 911 } 912 913 @Override 914 public synchronized boolean refresh() { 915 return refresh(false); 916 } 917 918 @Override 919 public synchronized boolean refresh(boolean reset) { 920 if (this.stash.isEmpty()) { 921 return false; 922 } 923 boolean requireStart; 924 if (reset) { 925 requireStart = reset(); 926 } else { 927 requireStart = stop(); 928 } 929 Stash currentStash = this.stash; 930 this.stash = new Stash(); 931 applyStash(currentStash); 932 if (requireStart) { 933 start(); 934 } 935 return true; 936 } 937 938 protected synchronized void restoreSnapshot() { 939 if (changed && snapshot != null) { 940 log.info("Restoring components snapshot"); 941 this.registry = new ComponentRegistry(snapshot); 942 changed = false; 943 } 944 } 945 946 /** 947 * Tests whether new registrations should be stashed at registration time. If the component manager was started then 948 * new components should be stashed otherwise they can be registered. 949 * <p /> 950 * TODO: current implementation is stashing after the start completion. Should we also stashing while start is in 951 * progress? 952 */ 953 protected boolean shouldStash() { 954 return isRunning() && !isFlushingStash; 955 } 956 957 protected synchronized void applyStash(Stash stash) { 958 log.info("Applying stashed components"); 959 isFlushingStash = true; 960 try { 961 for (ComponentName name : stash.toRemove) { 962 unregister(name); 963 } 964 for (RegistrationInfo ri : stash.toAdd) { 965 register(ri); 966 } 967 } finally { 968 isFlushingStash = false; 969 } 970 } 971 972 @Override 973 public synchronized void unstash() { 974 Stash currentStash = this.stash; 975 this.stash = new Stash(); 976 977 if (!isRunning()) { 978 applyStash(currentStash); 979 } else { 980 try { 981 applyStashWhenRunning(currentStash); 982 } catch (InterruptedException e) { 983 Thread.currentThread().interrupt(); 984 throw new RuntimeException("Interrupted while unstashing components", e); 985 } 986 } 987 } 988 989 private void applyStashWhenRunning(Stash stash) throws InterruptedException { 990 List<RegistrationInfo> toRemove = stash.getRegistrationsToRemove(registry); 991 if (isStarted()) { 992 for (RegistrationInfo ri : toRemove) { 993 this.started.remove(ri); 994 stopComponent(ri); 995 } 996 } 997 for (RegistrationInfo ri : toRemove) { 998 if (isStandby()) { 999 this.standby.remove(ri); 1000 } 1001 deactivateComponent(ri, false); 1002 } 1003 1004 applyStash(stash); 1005 1006 // activate the new components 1007 for (RegistrationInfo ri : stash.toAdd) { 1008 if (ri.isResolved()) { 1009 activateComponent(ri); 1010 } 1011 } 1012 if (isStandby()) { 1013 // activate the new components 1014 for (RegistrationInfo ri : stash.toAdd) { 1015 if (ri.isResolved()) { 1016 activateComponent(ri); 1017 // add new components to standby list 1018 this.standby.add(ri); 1019 } 1020 } 1021 } else if (isStarted()) { 1022 // start the new components and add them to the started list 1023 for (RegistrationInfo ri : stash.toAdd) { 1024 if (ri.isResolved()) { 1025 activateComponent(ri); 1026 } 1027 } 1028 for (RegistrationInfo ri : stash.toAdd) { 1029 if (ri.isActivated()) { 1030 startComponent(ri); 1031 this.started.add(ri); 1032 } 1033 } 1034 } 1035 } 1036 1037 /** 1038 * TODO we use for now the same sorter as OSGIRuntimeService - should be improved later. 1039 */ 1040 protected static class RIApplicationStartedComparator implements Comparator<RegistrationInfo> { 1041 1042 @Override 1043 public int compare(RegistrationInfo r1, RegistrationInfo r2) { 1044 int cmp = Integer.compare(r1.getApplicationStartedOrder(), r2.getApplicationStartedOrder()); 1045 if (cmp == 0) { 1046 // fallback on name order, to be deterministic 1047 cmp = r1.getName().getName().compareTo(r2.getName().getName()); 1048 } 1049 return cmp; 1050 } 1051 1052 } 1053 1054 protected void writeDevMetrics(Watch watch, String type) { 1055 if (!Framework.isDevModeSet()) { 1056 return; 1057 } 1058 File file = new File(Environment.getDefault().getTemp(), type + "-metrics.txt"); 1059 try (PrintStream ps = new PrintStream(new FileOutputStream(file), false, "UTF-8")) { 1060 ps.println(watch.getTotal()); 1061 // print first the longest intervals 1062 Arrays.stream(watch.getIntervals()).sorted(Comparator.reverseOrder()).forEach(ps::println); 1063 ps.flush(); 1064 } catch (IOException e) { 1065 log.error("Failed to write metrics file: " + file, e); 1066 } 1067 } 1068 1069 /** 1070 * Log a warning message if the timeout is reached while executing the given runnable. 1071 */ 1072 protected static void runWihtinTimeout(long timeout, TimeUnit unit, String warn, Runnable runnable) 1073 throws InterruptedException { 1074 ExecutorService executor = Executors.newSingleThreadExecutor(); 1075 try { 1076 Future<?> future = executor.submit(runnable::run); 1077 executor.shutdown(); 1078 try { 1079 try { 1080 future.get(timeout, unit); 1081 } catch (TimeoutException cause) { 1082 log.warn(warn); 1083 future.get(); 1084 } 1085 } catch (ExecutionException cause) { 1086 throw new RuntimeException("Errors caught while stopping components, giving up", cause); 1087 } 1088 } finally { 1089 executor.shutdownNow(); 1090 } 1091 } 1092 1093 protected class Listeners { 1094 1095 protected ListenerList listeners = new ListenerList(); 1096 1097 public void add(ComponentManager.Listener listener) { 1098 listeners.add(listener); 1099 } 1100 1101 public void remove(ComponentManager.Listener listener) { 1102 listeners.remove(listener); 1103 } 1104 1105 public void beforeActivation() { 1106 for (Object listener : listeners.getListeners()) { 1107 ((ComponentManager.Listener) listener).beforeActivation(ComponentManagerImpl.this); 1108 } 1109 } 1110 1111 public void afterActivation() { 1112 for (Object listener : listeners.getListeners()) { 1113 ((ComponentManager.Listener) listener).afterActivation(ComponentManagerImpl.this); 1114 } 1115 } 1116 1117 public void beforeDeactivation() { 1118 for (Object listener : listeners.getListeners()) { 1119 ((ComponentManager.Listener) listener).beforeDeactivation(ComponentManagerImpl.this); 1120 } 1121 } 1122 1123 public void afterDeactivation() { 1124 for (Object listener : listeners.getListeners()) { 1125 ((ComponentManager.Listener) listener).afterDeactivation(ComponentManagerImpl.this); 1126 } 1127 } 1128 1129 public void beforeStart(boolean isResume) { 1130 for (Object listener : listeners.getListeners()) { 1131 ((ComponentManager.Listener) listener).beforeStart(ComponentManagerImpl.this, isResume); 1132 } 1133 } 1134 1135 public void afterStart(boolean isResume) { 1136 for (Object listener : listeners.getListeners()) { 1137 ((ComponentManager.Listener) listener).afterStart(ComponentManagerImpl.this, isResume); 1138 } 1139 } 1140 1141 public void beforeStop(boolean isStandby) { 1142 for (Object listener : listeners.getListeners()) { 1143 ((ComponentManager.Listener) listener).beforeStop(ComponentManagerImpl.this, isStandby); 1144 } 1145 } 1146 1147 public void afterStop(boolean isStandby) { 1148 for (Object listener : listeners.getListeners()) { 1149 ((ComponentManager.Listener) listener).afterStop(ComponentManagerImpl.this, isStandby); 1150 } 1151 } 1152 1153 } 1154 1155 protected static class Stash { 1156 1157 protected volatile List<RegistrationInfo> toAdd; 1158 1159 protected volatile Set<ComponentName> toRemove; 1160 1161 public Stash() { 1162 toAdd = new ArrayList<>(); 1163 toRemove = new HashSet<>(); 1164 } 1165 1166 public void add(RegistrationInfo ri) { 1167 this.toAdd.add(ri); 1168 } 1169 1170 public void remove(ComponentName name) { 1171 this.toRemove.add(name); 1172 } 1173 1174 public boolean isEmpty() { 1175 return toAdd.isEmpty() && toRemove.isEmpty(); 1176 } 1177 1178 public List<RegistrationInfo> getRegistrationsToRemove(ComponentRegistry reg) { 1179 List<RegistrationInfo> ris = new ArrayList<>(); 1180 for (ComponentName name : toRemove) { 1181 RegistrationInfo ri = reg.getComponent(name); 1182 if (ri != null) { 1183 ris.add(ri); 1184 } 1185 } 1186 return ris; 1187 } 1188 1189 } 1190 1191}