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