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}