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, RegistrationInfoImpl> 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<RegistrationInfoImpl> 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<RegistrationInfoImpl> 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 regInfo) { 240 RegistrationInfoImpl ri = (RegistrationInfoImpl) regInfo; 241 ComponentName name = ri.getName(); 242 if (blacklist.contains(name.getName())) { 243 log.warn("Component " + name.getName() + " was blacklisted. Ignoring."); 244 return; 245 } 246 247 Set<ComponentName> componentsToRemove = stash.toRemove; 248 // Look if the component is not going to be removed when applying the stash 249 // before checking for duplicates. 250 if (!componentsToRemove.contains(name)) { 251 if (registry.contains(name)) { 252 if (name.getName().startsWith("org.nuxeo.runtime.")) { 253 // XXX we hide the fact that nuxeo-runtime bundles are 254 // registered twice 255 // TODO fix the root cause and remove this 256 return; 257 } 258 handleError("Duplicate component name: " + name, null); 259 return; 260 } 261 for (ComponentName n : ri.getAliases()) { 262 if (registry.contains(n)) { 263 handleError("Duplicate component name: " + n + " (alias for " + name + ")", null); 264 return; 265 } 266 } 267 } 268 269 if (shouldStash()) { // stash the registration 270 // should stash before calling ri.attach. 271 stash.add(ri); 272 return; 273 } 274 275 if (hasSnapshot()) { 276 // we are modifying the registry after the snapshot was created 277 changed = true; 278 } 279 280 ri.attach(this); 281 282 try { 283 log.info("Registering component: " + name); 284 if (!registry.addComponent(ri)) { 285 log.info("Registration delayed for component: " + name + ". Waiting for: " 286 + registry.getMissingDependencies(ri.getName())); 287 } 288 } catch (RuntimeException e) { 289 // don't raise this exception, 290 // we want to isolate component errors from other components 291 handleError("Failed to register component: " + name + " (" + e.toString() + ')', e); 292 } 293 } 294 295 @Override 296 public synchronized void unregister(RegistrationInfo regInfo) { 297 unregister(regInfo.getName()); 298 } 299 300 @Override 301 public synchronized void unregister(ComponentName name) { 302 if (shouldStash()) { // stash the un-registration 303 stash.remove(name); 304 return; 305 } 306 if (hasSnapshot()) { 307 changed = true; 308 } 309 try { 310 log.info("Unregistering component: " + name); 311 registry.removeComponent(name); 312 } catch (RuntimeException e) { 313 log.error("Failed to unregister component: " + name, e); 314 } 315 } 316 317 @Override 318 public synchronized boolean unregisterByLocation(String sourceId) { 319 ComponentName name = registry.deployedFiles.remove(sourceId); 320 if (name != null) { 321 unregister(name); 322 return true; 323 } else { 324 return false; 325 } 326 } 327 328 @Override 329 public boolean hasComponentFromLocation(String sourceId) { 330 return registry.deployedFiles.containsKey(sourceId); 331 } 332 333 @Override 334 public void addComponentListener(ComponentListener listener) { 335 compListeners.add(listener); 336 } 337 338 @Override 339 public void removeComponentListener(ComponentListener listener) { 340 compListeners.remove(listener); 341 } 342 343 @Override 344 public void addListener(ComponentManager.Listener listener) { 345 listeners.add(listener); 346 } 347 348 @Override 349 public void removeListener(ComponentManager.Listener listener) { 350 listeners.remove(listener); 351 } 352 353 @Override 354 public ComponentInstance getComponentProvidingService(Class<?> serviceClass) { 355 RegistrationInfoImpl ri = services.get(serviceClass.getName()); 356 if (ri == null) { 357 return null; 358 } 359 ComponentInstance ci = ri.getComponent(); 360 if (ci == null) { 361 if (log.isDebugEnabled()) { 362 log.debug("The component exposing the service " + serviceClass + " is not resolved or not started"); 363 } 364 } 365 return ci; 366 } 367 368 @Override 369 public <T> T getService(Class<T> serviceClass) { 370 ComponentInstance comp = getComponentProvidingService(serviceClass); 371 return comp != null ? comp.getAdapter(serviceClass) : null; 372 } 373 374 @Override 375 public Collection<ComponentName> getActivatingRegistrations() { 376 return getRegistrations(RegistrationInfo.ACTIVATING); 377 } 378 379 @Override 380 public Collection<ComponentName> getStartFailureRegistrations() { 381 return getRegistrations(RegistrationInfo.START_FAILURE); 382 } 383 384 protected Collection<ComponentName> getRegistrations(int state) { 385 RegistrationInfo[] comps = registry.getComponentsArray(); 386 Collection<ComponentName> ret = new ArrayList<>(); 387 for (RegistrationInfo ri : comps) { 388 if (ri.getState() == state) { 389 ret.add(ri.getName()); 390 } 391 } 392 return ret; 393 } 394 395 void sendEvent(ComponentEvent event) { 396 log.debug("Dispatching event: " + event); 397 Object[] listeners = this.compListeners.getListeners(); 398 for (Object listener : listeners) { 399 ((ComponentListener) listener).handleEvent(event); 400 } 401 } 402 403 public synchronized void registerExtension(Extension extension) { 404 ComponentName name = extension.getTargetComponent(); 405 RegistrationInfoImpl ri = registry.getComponent(name); 406 if (ri != null && ri.component != null) { 407 if (log.isDebugEnabled()) { 408 log.debug("Register contributed extension: " + extension); 409 } 410 loadContributions(ri, extension); 411 ri.component.registerExtension(extension); 412 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_REGISTERED, 413 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 414 } else { 415 // put the extension in the pending queue 416 if (log.isDebugEnabled()) { 417 log.debug("Enqueue contributed extension to pending queue: " + extension); 418 } 419 Set<Extension> extensions = pendingExtensions.get(name); 420 if (extensions == null) { 421 extensions = new LinkedHashSet<>(); // must keep order in which extensions are contributed 422 pendingExtensions.put(name, extensions); 423 } 424 extensions.add(extension); 425 sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_PENDING, 426 ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); 427 } 428 } 429 430 public synchronized void unregisterExtension(Extension extension) { 431 // TODO check if framework is shutting down and in that case do nothing 432 if (log.isDebugEnabled()) { 433 log.debug("Unregister contributed extension: " + extension); 434 } 435 ComponentName name = extension.getTargetComponent(); 436 RegistrationInfo ri = registry.getComponent(name); 437 if (ri != null) { 438 ComponentInstance co = ri.getComponent(); 439 if (co != null) { 440 co.unregisterExtension(extension); 441 } 442 } else { // maybe it's pending 443 Set<Extension> extensions = pendingExtensions.get(name); 444 if (extensions != null) { 445 // FIXME: extensions is a set of Extensions, not ComponentNames. 446 extensions.remove(name); 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(RegistrationInfoImpl ri, Extension xt) { 457 ExtensionPointImpl xp = ri.getExtensionPoint(xt.getExtensionPoint()); 458 if (xp != null && xp.contributions != null) { 459 try { 460 Object[] contribs = xp.loadContributions(ri, xt); 461 xt.setContributions(contribs); 462 } catch (RuntimeException e) { 463 handleError("Failed to load contributions for component " + xt.getComponent().getName(), e); 464 } 465 } 466 } 467 468 public synchronized void registerServices(RegistrationInfoImpl ri) { 469 if (ri.serviceDescriptor == null) { 470 return; 471 } 472 for (String service : ri.serviceDescriptor.services) { 473 log.info("Registering service: " + service); 474 services.put(service, ri); 475 // TODO: send notifications 476 } 477 } 478 479 public synchronized void unregisterServices(RegistrationInfoImpl ri) { 480 if (ri.serviceDescriptor == null) { 481 return; 482 } 483 for (String service : ri.serviceDescriptor.services) { 484 services.remove(service); 485 // TODO: send notifications 486 } 487 } 488 489 @Override 490 public String[] getServices() { 491 return services.keySet().toArray(new String[services.size()]); 492 } 493 494 protected static void handleError(String message, Exception e) { 495 log.error(message, e); 496 Framework.getRuntime().getWarnings().add(message); 497 } 498 499 /** 500 * Activate all the resolved components and return the list of activated components in the activation order 501 * 502 * @return the list of the activated components in the activation order 503 * @since 9.2 504 */ 505 protected List<RegistrationInfoImpl> activateComponents() { 506 Watch watch = new Watch(); 507 watch.start(); 508 listeners.beforeActivation(); 509 // make sure we start with a clean pending registry 510 pendingExtensions.clear(); 511 512 List<RegistrationInfoImpl> ris = new ArrayList<>(); 513 // first activate resolved components 514 for (RegistrationInfoImpl ri : registry.getResolvedRegistrationInfo()) { 515 // TODO catch and handle errors 516 watch.start(ri.getName().getName()); 517 ri.activate(); 518 ris.add(ri); 519 watch.stop(ri.getName().getName()); 520 } 521 listeners.afterActivation(); 522 watch.stop(); 523 524 if (infoLog.isInfoEnabled()) { 525 infoLog.info("Components activated in " + watch.total.formatSeconds() + " sec."); 526 } 527 writeDevMetrics(watch, "activate"); 528 529 return ris; 530 } 531 532 /** 533 * Deactivate all active components in the reverse resolve order 534 * 535 * @since 9.2 536 */ 537 protected void deactivateComponents() { 538 Watch watch = new Watch(); 539 watch.start(); 540 listeners.beforeDeactivation(); 541 Collection<RegistrationInfoImpl> resolved = registry.getResolvedRegistrationInfo(); 542 List<RegistrationInfoImpl> reverseResolved = new ArrayList<>(resolved); 543 Collections.reverse(reverseResolved); 544 for (RegistrationInfoImpl ri : reverseResolved) { 545 if (ri.isActivated()) { 546 watch.start(ri.getName().getName()); 547 ri.deactivate(false); 548 watch.stop(ri.getName().getName()); 549 } 550 } 551 // make sure the pending extension map is empty since we didn't unregistered extensions by calling 552 // ri.deactivate(false) 553 pendingExtensions.clear(); 554 listeners.afterDeactivation(); 555 watch.stop(); 556 557 writeDevMetrics(watch, "deactivate"); 558 } 559 560 /** 561 * Start all given components 562 * 563 * @since 9.2 564 */ 565 protected void startComponents(List<RegistrationInfoImpl> ris, boolean isResume) { 566 Watch watch = new Watch(); 567 watch.start(); 568 listeners.beforeStart(isResume); 569 for (RegistrationInfoImpl ri : ris) { 570 watch.start(ri.getName().getName()); 571 ri.start(); 572 watch.stop(ri.getName().getName()); 573 } 574 this.started = ris; 575 listeners.afterStart(isResume); 576 watch.stop(); 577 578 infoLog.info("Components started in " + watch.total.formatSeconds() + " sec."); 579 writeDevMetrics(watch, "start"); 580 } 581 582 /** 583 * Stop all started components. Stopping components is done in reverse start order. 584 * 585 * @since 9.2 586 */ 587 protected void stopComponents(boolean isStandby) { 588 try { 589 doStopComppnents(isStandby); 590 } catch (InterruptedException e) { 591 Thread.currentThread().interrupt(); 592 throw new RuntimeException("Interrupted while stopping components", e); 593 } 594 } 595 596 private void doStopComppnents(boolean isStandby) throws InterruptedException { 597 Watch watch = new Watch(); 598 watch.start(); 599 listeners.beforeStop(isStandby); 600 List<RegistrationInfoImpl> list = this.started; 601 for (int i = list.size() - 1; i >= 0; i--) { 602 RegistrationInfoImpl ri = list.get(i); 603 if (ri.isStarted()) { 604 watch.start(ri.getName().getName()); 605 ri.stop(); 606 watch.stop(ri.getName().getName()); 607 } 608 } 609 listeners.afterStop(isStandby); 610 watch.stop(); 611 612 writeDevMetrics(watch, "stop"); 613 } 614 615 @Override 616 public synchronized boolean start() { 617 if (this.started != null) { 618 return false; 619 } 620 621 infoLog.info("Starting Nuxeo Components"); 622 623 List<RegistrationInfoImpl> ris = activateComponents(); 624 625 // TODO we sort using the old start order sorter (see OSGiRuntimeService.RIApplicationStartedComparator) 626 Collections.sort(ris, new RIApplicationStartedComparator()); 627 628 // then start activated components 629 startComponents(ris, false); 630 631 return true; 632 } 633 634 @Override 635 public synchronized boolean stop() { 636 if (this.started == null) { 637 return false; 638 } 639 640 infoLog.info("Stopping Nuxeo Components"); 641 642 try { 643 stopComponents(false); 644 // now deactivate all active components 645 deactivateComponents(); 646 } finally { 647 this.started = null; 648 } 649 650 return true; 651 } 652 653 @Override 654 public void stop(int timeoutInSeconds) { 655 try { 656 runWihtinTimeout(timeoutInSeconds, TimeUnit.SECONDS, "Timed out on stop, blocking", this::stop); 657 } catch (InterruptedException e) { 658 Thread.currentThread().interrupt(); 659 throw new RuntimeException("Interrupted while stopping components", e); 660 } 661 } 662 663 @Override 664 public synchronized void standby() { 665 if (this.started != null) { 666 try { 667 stopComponents(true); 668 } finally { 669 this.standby = this.started; 670 this.started = null; 671 } 672 } 673 } 674 675 @Override 676 public void standby(int timeoutInSeconds) { 677 try { 678 runWihtinTimeout(timeoutInSeconds, TimeUnit.SECONDS, "Timed out on standby, blocking", this::standby); 679 } catch (InterruptedException e) { 680 Thread.currentThread().interrupt(); 681 throw new RuntimeException("Interrupted while standbying components", e); 682 } 683 } 684 685 @Override 686 public synchronized void resume() { 687 if (this.standby != null) { 688 try { 689 startComponents(this.standby, true); 690 } finally { 691 this.started = this.standby; 692 this.standby = null; 693 } 694 } 695 } 696 697 @Override 698 public boolean isStarted() { 699 return this.started != null; 700 } 701 702 @Override 703 public boolean isStandby() { 704 return this.standby != null; 705 } 706 707 @Override 708 public boolean isRunning() { 709 return this.started != null || this.standby != null; 710 } 711 712 @Override 713 public boolean hasSnapshot() { 714 return this.snapshot != null; 715 } 716 717 @Override 718 public boolean hasChanged() { 719 return this.changed; 720 } 721 722 @Override 723 public synchronized void snapshot() { 724 this.snapshot = new ComponentRegistry(registry); 725 } 726 727 @Override 728 public boolean isStashEmpty() { 729 return stash.isEmpty(); 730 } 731 732 @Override 733 public synchronized void restart(boolean reset) { 734 if (reset) { 735 this.reset(); 736 } else { 737 this.stop(); 738 } 739 this.start(); 740 } 741 742 @Override 743 public synchronized boolean reset() { 744 boolean r = this.stop(); 745 restoreSnapshot(); 746 return r; 747 } 748 749 @Override 750 public synchronized boolean refresh() { 751 return refresh(false); 752 } 753 754 @Override 755 public synchronized boolean refresh(boolean reset) { 756 if (this.stash.isEmpty()) { 757 return false; 758 } 759 boolean requireStart; 760 if (reset) { 761 requireStart = reset(); 762 } else { 763 requireStart = stop(); 764 } 765 Stash currentStash = this.stash; 766 this.stash = new Stash(); 767 applyStash(currentStash); 768 if (requireStart) { 769 start(); 770 } 771 return true; 772 } 773 774 protected synchronized void restoreSnapshot() { 775 if (changed && snapshot != null) { 776 log.info("Restoring components snapshot"); 777 this.registry = new ComponentRegistry(snapshot); 778 changed = false; 779 } 780 } 781 782 /** 783 * Tests whether new registrations should be stashed at registration time. If the component manager was started then 784 * new components should be stashed otherwise they can be registered. When in standby mode components are not 785 * stashed. 786 * <p /> 787 * TODO: current implementation is stashing after the start completion. Should we also stashing while start is in 788 * progress? 789 */ 790 protected boolean shouldStash() { 791 return this.started != null && !isFlushingStash; 792 } 793 794 protected synchronized void applyStash(Stash stash) { 795 log.info("Applying stashed components"); 796 isFlushingStash = true; 797 try { 798 for (ComponentName name : stash.toRemove) { 799 unregister(name); 800 } 801 for (RegistrationInfoImpl ri : stash.toAdd) { 802 register(ri); 803 } 804 } finally { 805 isFlushingStash = false; 806 } 807 } 808 809 @Override 810 public synchronized void unstash() { 811 Stash currentStash = this.stash; 812 this.stash = new Stash(); 813 814 if (!isRunning()) { 815 applyStash(currentStash); 816 } else { 817 try { 818 applyStashWhenRunning(currentStash); 819 } catch (InterruptedException e) { 820 Thread.currentThread().interrupt(); 821 throw new RuntimeException("Interrupted while unstashing components", e); 822 } 823 } 824 } 825 826 private void applyStashWhenRunning(Stash stash) throws InterruptedException { 827 List<RegistrationInfoImpl> toRemove = stash.getRegistrationsToRemove(registry); 828 if (isStarted()) { 829 for (RegistrationInfoImpl ri : toRemove) { 830 this.started.remove(ri); 831 ri.stop(); 832 } 833 } 834 for (RegistrationInfoImpl ri : toRemove) { 835 if (isStandby()) { 836 this.standby.remove(ri); 837 } 838 ri.deactivate(); 839 } 840 841 applyStash(stash); 842 843 // activate the new components 844 for (RegistrationInfoImpl ri : stash.toAdd) { 845 if (ri.isResolved()) { 846 ri.activate(); 847 } 848 } 849 if (isStandby()) { 850 // activate the new components 851 for (RegistrationInfoImpl ri : stash.toAdd) { 852 if (ri.isResolved()) { 853 ri.activate(); 854 // add new components to standby list 855 this.standby.add(ri); 856 } 857 } 858 } else if (isStarted()) { 859 // start the new components and add them to the started list 860 for (RegistrationInfoImpl ri : stash.toAdd) { 861 if (ri.isResolved()) { 862 ri.activate(); 863 } 864 } 865 for (RegistrationInfoImpl ri : stash.toAdd) { 866 if (ri.isActivated()) { 867 ri.start(); 868 this.started.add(ri); 869 } 870 } 871 } 872 } 873 874 /** 875 * TODO we use for now the same sorter as OSGIRuntimeService - should be improved later. 876 */ 877 protected static class RIApplicationStartedComparator implements Comparator<RegistrationInfo> { 878 879 @Override 880 public int compare(RegistrationInfo r1, RegistrationInfo r2) { 881 int cmp = Integer.compare(r1.getApplicationStartedOrder(), r2.getApplicationStartedOrder()); 882 if (cmp == 0) { 883 // fallback on name order, to be deterministic 884 cmp = r1.getName().getName().compareTo(r2.getName().getName()); 885 } 886 return cmp; 887 } 888 889 } 890 891 protected void writeDevMetrics(Watch watch, String type) { 892 if (!Framework.isDevModeSet()) { 893 return; 894 } 895 File file = new File(Environment.getDefault().getTemp(), type + "-metrics.txt"); 896 try (PrintStream ps = new PrintStream(new FileOutputStream(file), false, "UTF-8")) { 897 ps.println(watch.getTotal()); 898 // print first the longest intervals 899 Arrays.stream(watch.getIntervals()).sorted(Comparator.reverseOrder()).forEach(ps::println); 900 ps.flush(); 901 } catch (IOException e) { 902 log.error("Failed to write metrics file: " + file, e); 903 } 904 } 905 906 /** 907 * Log a warning message if the timeout is reached while executing the given runnable. 908 */ 909 protected static void runWihtinTimeout(long timeout, TimeUnit unit, String warn, Runnable runnable) 910 throws InterruptedException { 911 ExecutorService executor = Executors.newSingleThreadExecutor(); 912 try { 913 Future<?> future = executor.submit(runnable::run); 914 executor.shutdown(); 915 try { 916 try { 917 future.get(timeout, unit); 918 } catch (TimeoutException cause) { 919 log.warn(warn); 920 future.get(); 921 } 922 } catch (ExecutionException cause) { 923 throw new RuntimeException("Errors caught while stopping components, giving up", cause); 924 } 925 } finally { 926 executor.shutdownNow(); 927 } 928 } 929 930 protected class Listeners { 931 932 protected ListenerList listeners = new ListenerList(); 933 934 public void add(ComponentManager.Listener listener) { 935 listeners.add(listener); 936 } 937 938 public void remove(ComponentManager.Listener listener) { 939 listeners.remove(listener); 940 } 941 942 public void beforeActivation() { 943 for (Object listener : listeners.getListeners()) { 944 ((ComponentManager.Listener) listener).beforeActivation(ComponentManagerImpl.this); 945 } 946 } 947 948 public void afterActivation() { 949 for (Object listener : listeners.getListeners()) { 950 ((ComponentManager.Listener) listener).afterActivation(ComponentManagerImpl.this); 951 } 952 } 953 954 public void beforeDeactivation() { 955 for (Object listener : listeners.getListeners()) { 956 ((ComponentManager.Listener) listener).beforeDeactivation(ComponentManagerImpl.this); 957 } 958 } 959 960 public void afterDeactivation() { 961 for (Object listener : listeners.getListeners()) { 962 ((ComponentManager.Listener) listener).afterDeactivation(ComponentManagerImpl.this); 963 } 964 } 965 966 public void beforeStart(boolean isResume) { 967 for (Object listener : listeners.getListeners()) { 968 ((ComponentManager.Listener) listener).beforeStart(ComponentManagerImpl.this, isResume); 969 } 970 } 971 972 public void afterStart(boolean isResume) { 973 for (Object listener : listeners.getListeners()) { 974 ((ComponentManager.Listener) listener).afterStart(ComponentManagerImpl.this, isResume); 975 } 976 } 977 978 public void beforeStop(boolean isStandby) { 979 for (Object listener : listeners.getListeners()) { 980 ((ComponentManager.Listener) listener).beforeStop(ComponentManagerImpl.this, isStandby); 981 } 982 } 983 984 public void afterStop(boolean isStandby) { 985 for (Object listener : listeners.getListeners()) { 986 ((ComponentManager.Listener) listener).afterStop(ComponentManagerImpl.this, isStandby); 987 } 988 } 989 990 } 991 992 protected static class Stash { 993 994 protected volatile List<RegistrationInfoImpl> toAdd; 995 996 protected volatile Set<ComponentName> toRemove; 997 998 public Stash() { 999 toAdd = new ArrayList<>(); 1000 toRemove = new HashSet<>(); 1001 } 1002 1003 public void add(RegistrationInfoImpl ri) { 1004 this.toAdd.add(ri); 1005 } 1006 1007 public void remove(ComponentName name) { 1008 this.toRemove.add(name); 1009 } 1010 1011 public boolean isEmpty() { 1012 return toAdd.isEmpty() && toRemove.isEmpty(); 1013 } 1014 1015 public List<RegistrationInfoImpl> getRegistrationsToRemove(ComponentRegistry reg) { 1016 ArrayList<RegistrationInfoImpl> ris = new ArrayList<>(); 1017 for (ComponentName name : toRemove) { 1018 RegistrationInfoImpl ri = reg.getComponent(name); 1019 if (ri != null) { 1020 ris.add(ri); 1021 } 1022 } 1023 return ris; 1024 } 1025 1026 } 1027 1028}