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