001/*
002 * (C) Copyright 2006-2018 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.logging.log4j.LogManager;
047import org.apache.logging.log4j.Logger;
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.DescriptorRegistry;
058import org.nuxeo.runtime.model.Extension;
059import org.nuxeo.runtime.model.RegistrationInfo;
060import org.nuxeo.runtime.util.Watch;
061
062/**
063 * @author Bogdan Stefanescu
064 * @author Florent Guillaume
065 */
066public class ComponentManagerImpl implements ComponentManager {
067
068    private static final Logger log = LogManager.getLogger(ComponentManagerImpl.class);
069
070    // must use an ordered Set to avoid loosing the order of the pending
071    // extensions
072    protected final ConcurrentMap<ComponentName, Set<Extension>> pendingExtensions;
073
074    private ListenerList compListeners;
075
076    /**
077     * Manager listeners. Listen too events like start stop restart etc.
078     *
079     * @since 9.2
080     */
081    private Listeners listeners;
082
083    private final ConcurrentMap<String, RegistrationInfo> services;
084
085    protected volatile Set<String> blacklist;
086
087    /**
088     * The list of started components (sorted according to the start order). This list is null if the components were
089     * not yet started or were stopped
090     *
091     * @since 9.2
092     */
093    protected volatile List<RegistrationInfo> started;
094
095    /**
096     * The list of standby components (sorted according to the start order) This list is null if component were not yet
097     * started or not yet put in standby When putting components in standby all started components are stopped and the
098     * {@link #started} list is assigned to {@link #standby} list then the {@link #started} field is nullified. When
099     * resuming standby components the started list is restored from the standby list and the standby field is nullified
100     *
101     * @since 9.2
102     */
103    protected volatile List<RegistrationInfo> standby;
104
105    /**
106     * A list of registrations that were deployed while the manager was started.
107     *
108     * @since 9.2
109     */
110    protected volatile Stash stash;
111
112    /**
113     * @since 9.2
114     */
115    protected volatile ComponentRegistry registry;
116
117    /**
118     * @since 9.2
119     */
120    protected volatile ComponentRegistry snapshot;
121
122    /**
123     * @since 10.3
124     */
125    protected volatile DescriptorRegistry descriptors;
126
127    /**
128     * @since 9.2
129     */
130    protected volatile boolean isFlushingStash = false;
131
132    /**
133     * @since 9.2
134     */
135    protected volatile boolean changed = false;
136
137    public ComponentManagerImpl(RuntimeService runtime) {
138        registry = new ComponentRegistry();
139        pendingExtensions = new ConcurrentHashMap<>();
140        compListeners = new ListenerList();
141        listeners = new Listeners();
142        services = new ConcurrentHashMap<>();
143        blacklist = new HashSet<>();
144        stash = new Stash();
145        descriptors = new DescriptorRegistry();
146    }
147
148    /**
149     * @since 10.3
150     */
151    public DescriptorRegistry getDescriptors() {
152        return descriptors;
153    }
154
155    /**
156     * @since 9.2
157     */
158    public final ComponentRegistry getRegistry() {
159        return registry;
160    }
161
162    @Override
163    public Collection<RegistrationInfo> getRegistrations() {
164        return registry.getComponents();
165    }
166
167    /**
168     * @since 9.2
169     */
170    @Override
171    public Collection<ComponentName> getResolvedRegistrations() {
172        return registry.getResolvedNames();
173    }
174
175    @Override
176    public synchronized Map<ComponentName, Set<ComponentName>> getPendingRegistrations() {
177        Map<ComponentName, Set<ComponentName>> pending = new HashMap<>();
178        for (Map.Entry<ComponentName, Set<ComponentName>> p : registry.getPendingComponents().entrySet()) {
179            pending.put(p.getKey(), new LinkedHashSet<>(p.getValue()));
180        }
181        return pending;
182    }
183
184    @Override
185    public synchronized Map<ComponentName, Set<Extension>> getMissingRegistrations() {
186        Map<ComponentName, Set<Extension>> missing = new HashMap<>();
187        // also add pending extensions, not resolved because of missing target extension point
188        for (Set<Extension> p : pendingExtensions.values()) {
189            for (Extension e : p) {
190                missing.computeIfAbsent(e.getComponent().getName(), k -> new LinkedHashSet<>()).add(e);
191            }
192        }
193        return missing;
194    }
195
196    /**
197     * Get the needed component names. The returned set is not a copy
198     */
199    public Set<ComponentName> getNeededRegistrations() {
200        return pendingExtensions.keySet();
201    }
202
203    /**
204     * Get the pending extensions. The returned set is not a copy
205     */
206    public Set<Extension> getPendingExtensions(ComponentName name) {
207        return pendingExtensions.get(name);
208    }
209
210    @Override
211    public RegistrationInfo getRegistrationInfo(ComponentName name) {
212        return registry.getComponent(name);
213    }
214
215    @Override
216    public boolean isRegistered(ComponentName name) {
217        return registry.contains(name);
218    }
219
220    @Override
221    public int size() {
222        return registry.size();
223    }
224
225    @Override
226    public ComponentInstance getComponent(ComponentName name) {
227        RegistrationInfo ri = registry.getComponent(name);
228        return ri != null ? ri.getComponent() : null;
229    }
230
231    @Override
232    public synchronized void shutdown() {
233        stop();
234        compListeners = null;
235        registry.destroy();
236        registry = null;
237        snapshot = null;
238    }
239
240    @Override
241    public Set<String> getBlacklist() {
242        return Collections.unmodifiableSet(blacklist);
243    }
244
245    @Override
246    public void setBlacklist(Set<String> blacklist) {
247        this.blacklist = blacklist;
248    }
249
250    @Override
251    public synchronized void register(RegistrationInfo ri) {
252        ComponentName name = ri.getName();
253        if (blacklist.contains(name.getName())) {
254            log.debug("Component {} was blacklisted. Ignoring.", name.getName());
255            return;
256        }
257
258        Set<ComponentName> componentsToRemove = stash.toRemove;
259        // Look if the component is not going to be removed when applying the stash
260        // before checking for duplicates.
261        if (!componentsToRemove.contains(name)) {
262            if (registry.contains(name)) {
263                if (name.getName().startsWith("org.nuxeo.runtime.")) {
264                    // XXX we hide the fact that nuxeo-runtime bundles are
265                    // registered twice
266                    // TODO fix the root cause and remove this
267                    return;
268                }
269                handleError("Duplicate component name: " + name, null);
270                return;
271            }
272            for (ComponentName n : ri.getAliases()) {
273                if (registry.contains(n)) {
274                    handleError("Duplicate component name: " + n + " (alias for " + name + ")", null);
275                    return;
276                }
277            }
278        }
279
280        if (shouldStash()) { // stash the registration
281            // should stash before calling ri.attach.
282            stash.add(ri);
283            return;
284        }
285
286        if (hasSnapshot()) {
287            // we are modifying the registry after the snapshot was created
288            changed = true;
289        }
290
291        // TODO it is just about giving manager to RegistrationInfo, do we need that ?
292        if (ri.useFormerLifecycleManagement()) {
293            ((RegistrationInfoImpl) ri).attach(this);
294        }
295
296        try {
297            log.debug("Registering component: {}", name);
298            if (!registry.addComponent(ri)) {
299                log.info("Registration delayed for component: " + name + ". Waiting for: "
300                        + registry.getMissingDependencies(ri.getName()));
301            }
302        } catch (RuntimeException e) {
303            // don't raise this exception,
304            // we want to isolate component errors from other components
305            handleError("Failed to register component: " + name + " (" + e.toString() + ')', e);
306        }
307    }
308
309    @Override
310    public synchronized void unregister(RegistrationInfo regInfo) {
311        unregister(regInfo.getName());
312    }
313
314    @Override
315    public synchronized void unregister(ComponentName name) {
316        if (shouldStash()) { // stash the un-registration
317            stash.remove(name);
318            return;
319        }
320        if (hasSnapshot()) {
321            changed = true;
322        }
323        try {
324            log.debug("Unregistering component: {}", name);
325            registry.removeComponent(name);
326        } catch (RuntimeException e) {
327            log.error("Failed to unregister component: {}", name, e);
328        }
329    }
330
331    @Override
332    public synchronized boolean unregisterByLocation(String sourceId) {
333        ComponentName name = registry.deployedFiles.remove(sourceId);
334        if (name != null) {
335            unregister(name);
336            return true;
337        } else {
338            return false;
339        }
340    }
341
342    @Override
343    public boolean hasComponentFromLocation(String sourceId) {
344        return registry.deployedFiles.containsKey(sourceId);
345    }
346
347    @Override
348    public void addComponentListener(ComponentListener listener) {
349        compListeners.add(listener);
350    }
351
352    @Override
353    public void removeComponentListener(ComponentListener listener) {
354        compListeners.remove(listener);
355    }
356
357    @Override
358    public void addListener(ComponentManager.Listener listener) {
359        listeners.add(listener);
360    }
361
362    @Override
363    public void removeListener(ComponentManager.Listener listener) {
364        listeners.remove(listener);
365    }
366
367    @Override
368    public ComponentInstance getComponentProvidingService(Class<?> serviceClass) {
369        RegistrationInfo ri = services.get(serviceClass.getName());
370        if (ri == null) {
371            return null;
372        }
373        ComponentInstance ci = ri.getComponent();
374        if (ci == null) {
375            log.debug("The component exposing the service {} is not resolved or not started", serviceClass);
376        }
377        return ci;
378    }
379
380    @Override
381    public <T> T getService(Class<T> serviceClass) {
382        ComponentInstance comp = getComponentProvidingService(serviceClass);
383        return comp != null ? comp.getAdapter(serviceClass) : null;
384    }
385
386    @Override
387    public Collection<ComponentName> getActivatingRegistrations() {
388        return getRegistrations(RegistrationInfo.ACTIVATING);
389    }
390
391    @Override
392    public Collection<ComponentName> getStartFailureRegistrations() {
393        return getRegistrations(RegistrationInfo.START_FAILURE);
394    }
395
396    protected Collection<ComponentName> getRegistrations(int state) {
397        RegistrationInfo[] comps = registry.getComponentsArray();
398        Collection<ComponentName> ret = new ArrayList<>();
399        for (RegistrationInfo ri : comps) {
400            if (ri.getState() == state) {
401                ret.add(ri.getName());
402            }
403        }
404        return ret;
405    }
406
407    void sendEvent(ComponentEvent event) {
408        log.trace("Dispatching event: {}", event);
409        Object[] listeners = this.compListeners.getListeners();
410        for (Object listener : listeners) {
411            ((ComponentListener) listener).handleEvent(event);
412        }
413    }
414
415    public synchronized void registerExtension(Extension extension) {
416        ComponentName name = extension.getTargetComponent();
417        RegistrationInfo ri = registry.getComponent(name);
418        if (ri != null && ri.getComponent() != null) {
419            log.debug("Register contributed extension: {}", extension);
420            loadContributions(ri, extension);
421            ri.getComponent().registerExtension(extension);
422            sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_REGISTERED,
423                    ((ComponentInstanceImpl) extension.getComponent()).ri, extension));
424        } else {
425            // put the extension in the pending queue
426            log.debug("Enqueue contributed extension to pending queue: {}", extension);
427            // must keep order in which extensions are contributed
428            pendingExtensions.computeIfAbsent(name, key -> new LinkedHashSet<>()).add(extension);
429            sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_PENDING,
430                    ((ComponentInstanceImpl) extension.getComponent()).ri, extension));
431        }
432    }
433
434    public synchronized void unregisterExtension(Extension extension) {
435        // TODO check if framework is shutting down and in that case do nothing
436        log.debug("Unregister contributed extension: {}", extension);
437        ComponentName name = extension.getTargetComponent();
438        RegistrationInfo ri = registry.getComponent(name);
439        if (ri != null) {
440            ComponentInstance co = ri.getComponent();
441            if (co != null) {
442                co.unregisterExtension(extension);
443            }
444        } else { // maybe it's pending
445            Set<Extension> extensions = pendingExtensions.get(name);
446            if (extensions != null) {
447                extensions.remove(extension);
448                if (extensions.isEmpty()) {
449                    pendingExtensions.remove(name);
450                }
451            }
452        }
453        sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_UNREGISTERED,
454                ((ComponentInstanceImpl) extension.getComponent()).ri, extension));
455    }
456
457    public static void loadContributions(RegistrationInfo ri, Extension xt) {
458        // in new java based system contributions don't need to be loaded, this is a XML specificity reflected by
459        // ExtensionPointImpl coming from XML deserialization
460        if (ri.useFormerLifecycleManagement()) {
461            // Extension point needing to load contribution are ExtensionPointImpl
462            ri.getExtensionPoint(xt.getExtensionPoint())
463              .filter(xp -> xp.getContributions() != null)
464              .map(ExtensionPointImpl.class::cast)
465              .ifPresent(xp -> {
466                  try {
467                      Object[] contribs = xp.loadContributions(ri, xt);
468                      xt.setContributions(contribs);
469                  } catch (RuntimeException e) {
470                      handleError("Failed to load contributions for component " + xt.getComponent().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, Exception e) {
503        log.error(message, e);
504        Framework.getRuntime().getMessageHandler().addWarning(message);
505    }
506
507    /**
508     * Activate all the resolved components and return the list of activated components in the activation order
509     *
510     * @return the list of the activated components in the activation order
511     * @since 9.2
512     */
513    protected List<RegistrationInfo> activateComponents() {
514        log.info("Activate components");
515        Watch watch = new Watch();
516        watch.start();
517        listeners.beforeActivation();
518        // make sure we start with a clean pending registry
519        pendingExtensions.clear();
520
521        List<RegistrationInfo> ris = new ArrayList<>();
522        // first activate resolved components
523        for (RegistrationInfo ri : registry.getResolvedRegistrationInfo()) {
524            // TODO catch and handle errors
525            watch.start(ri.getName().getName());
526            activateComponent(ri);
527            ris.add(ri);
528            watch.stop(ri.getName().getName());
529        }
530        listeners.afterActivation();
531        watch.stop();
532
533        log.debug("Components activated in {}s", watch.total::formatSeconds);
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.debug("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        log.info("Deactivate components");
613        Watch watch = new Watch();
614        watch.start();
615        listeners.beforeDeactivation();
616        Collection<RegistrationInfo> resolved = registry.getResolvedRegistrationInfo();
617        List<RegistrationInfo> reverseResolved = new ArrayList<>(resolved);
618        Collections.reverse(reverseResolved);
619        for (RegistrationInfo ri : reverseResolved) {
620            if (ri.isActivated()) {
621                watch.start(ri.getName().getName());
622                deactivateComponent(ri, isShutdown);
623                watch.stop(ri.getName().getName());
624            }
625        }
626        // make sure the pending extension map is empty since we didn't unregistered extensions by calling
627        // ri.deactivate(true)
628        pendingExtensions.clear();
629        listeners.afterDeactivation();
630        watch.stop();
631
632        log.debug("Components deactivated in {}s", watch.total::formatSeconds);
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        log.debug("Component deactivated: {}", ri.getName());
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        log.info("Start components (isResume={})", isResume);
686        Watch watch = new Watch();
687        watch.start();
688        listeners.beforeStart(isResume);
689        for (RegistrationInfo ri : ris) {
690            watch.start(ri.getName().getName());
691            startComponent(ri);
692            watch.stop(ri.getName().getName());
693        }
694        this.started = ris;
695        listeners.afterStart(isResume);
696        watch.stop();
697
698        log.debug("Components started in {}s", watch.total::formatSeconds);
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            log.debug("Component started: {}", ri.getName());
720            ri.setState(RegistrationInfo.STARTED);
721        } catch (RuntimeException e) {
722            log.error("Component {} notification of application started failed: {}", ri.getName(), 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        log.info("Stop components (isStandby={})", 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            log.debug("Components stopped in {}s", watch.total::formatSeconds);
751            writeDevMetrics(watch, "stop");
752        } catch (InterruptedException e) {
753            Thread.currentThread().interrupt();
754            throw new RuntimeException("Interrupted while stopping components", e);
755        }
756    }
757
758    /**
759     * Stops the given {@link RegistrationInfo}. This step will stop the component.
760     *
761     * @since 9.3
762     */
763    protected void stopComponent(RegistrationInfo ri) throws InterruptedException {
764        if (ri.useFormerLifecycleManagement()) {
765            ((RegistrationInfoImpl) ri).stop();
766            return;
767        }
768        if (ri.getState() != RegistrationInfo.STARTED) {
769            return;
770        }
771        ri.setState(RegistrationInfo.STOPPING);
772        ComponentInstance component = ri.getComponent();
773        component.stop();
774        log.debug("Component stopped: {}", ri.getName());
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        log.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        ris.sort(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        log.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.debug("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    /**
989     * Applies the given stash when Nuxeo is started or is standby.
990     * <p>
991     * Started state corresponds to activated & started components.
992     * <p>
993     * Standby state corresponds to activated components.
994     */
995    private void applyStashWhenRunning(Stash stash) throws InterruptedException {
996        List<RegistrationInfo> toRemove = stash.getRegistrationsToRemove(registry);
997        // Nuxeo is started so stop components to remove first and remove them from started list
998        if (isStarted()) {
999            for (RegistrationInfo ri : toRemove) {
1000                this.started.remove(ri);
1001                stopComponent(ri);
1002            }
1003        }
1004        // deactivate components to remove (and remove them from standby list if needed)
1005        for (RegistrationInfo ri : toRemove) {
1006            if (isStandby()) {
1007                this.standby.remove(ri);
1008            }
1009            deactivateComponent(ri, false);
1010        }
1011
1012        applyStash(stash);
1013
1014        // activate components to add (and add them to standby list if needed)
1015        for (RegistrationInfo ri : stash.toAdd) {
1016            if (ri.isResolved()) {
1017                activateComponent(ri);
1018                if (isStandby()) {
1019                    // add new components to standby list in order to start them latter
1020                    this.standby.add(ri);
1021                }
1022            }
1023        }
1024        // Nuxeo is started so start components to add and add them to the started list
1025        if (isStarted()) {
1026            for (RegistrationInfo ri : stash.toAdd) {
1027                if (ri.isActivated()) {
1028                    startComponent(ri);
1029                    this.started.add(ri);
1030                }
1031            }
1032        }
1033    }
1034
1035    /**
1036     * TODO we use for now the same sorter as OSGIRuntimeService - should be improved later.
1037     */
1038    protected static class RIApplicationStartedComparator implements Comparator<RegistrationInfo> {
1039
1040        @Override
1041        public int compare(RegistrationInfo r1, RegistrationInfo r2) {
1042            int cmp = Integer.compare(r1.getApplicationStartedOrder(), r2.getApplicationStartedOrder());
1043            if (cmp == 0) {
1044                // fallback on name order, to be deterministic
1045                cmp = r1.getName().getName().compareTo(r2.getName().getName());
1046            }
1047            return cmp;
1048        }
1049
1050    }
1051
1052    protected void writeDevMetrics(Watch watch, String type) {
1053        if (!Framework.isDevModeSet()) {
1054            return;
1055        }
1056        File file = new File(Environment.getDefault().getTemp(), type + "-metrics.txt");
1057        try (PrintStream ps = new PrintStream(new FileOutputStream(file), false, "UTF-8")) {
1058            ps.println(watch.getTotal());
1059            // print first the longest intervals
1060            Arrays.stream(watch.getIntervals()).sorted(Comparator.reverseOrder()).forEach(ps::println);
1061            ps.flush();
1062        } catch (IOException e) {
1063            log.error("Failed to write metrics file: {}", file, e);
1064        }
1065    }
1066
1067    /**
1068     * Log a warning message if the timeout is reached while executing the given runnable.
1069     */
1070    protected static void runWihtinTimeout(long timeout, TimeUnit unit, String warn, Runnable runnable)
1071            throws InterruptedException {
1072        ExecutorService executor = Executors.newSingleThreadExecutor();
1073        try {
1074            Future<?> future = executor.submit(runnable::run);
1075            executor.shutdown();
1076            try {
1077                try {
1078                    future.get(timeout, unit);
1079                } catch (TimeoutException cause) {
1080                    log.warn(warn);
1081                    future.get();
1082                }
1083            } catch (ExecutionException cause) {
1084                throw new RuntimeException("Errors caught while stopping components, giving up", cause);
1085            }
1086        } finally {
1087            executor.shutdownNow();
1088        }
1089    }
1090
1091    protected class Listeners {
1092
1093        protected ListenerList listeners = new ListenerList();
1094
1095        public void add(ComponentManager.Listener listener) {
1096            listeners.add(listener);
1097        }
1098
1099        public void remove(ComponentManager.Listener listener) {
1100            listeners.remove(listener);
1101        }
1102
1103        public void beforeActivation() {
1104            for (Object listener : listeners.getListeners()) {
1105                ((ComponentManager.Listener) listener).beforeActivation(ComponentManagerImpl.this);
1106            }
1107        }
1108
1109        public void afterActivation() {
1110            for (Object listener : listeners.getListeners()) {
1111                ((ComponentManager.Listener) listener).afterActivation(ComponentManagerImpl.this);
1112            }
1113        }
1114
1115        public void beforeDeactivation() {
1116            for (Object listener : listeners.getListeners()) {
1117                ((ComponentManager.Listener) listener).beforeDeactivation(ComponentManagerImpl.this);
1118            }
1119        }
1120
1121        public void afterDeactivation() {
1122            for (Object listener : listeners.getListeners()) {
1123                ((ComponentManager.Listener) listener).afterDeactivation(ComponentManagerImpl.this);
1124            }
1125        }
1126
1127        public void beforeStart(boolean isResume) {
1128            for (Object listener : listeners.getListeners()) {
1129                ((ComponentManager.Listener) listener).beforeStart(ComponentManagerImpl.this, isResume);
1130            }
1131        }
1132
1133        public void afterStart(boolean isResume) {
1134            for (Object listener : listeners.getListeners()) {
1135                ((ComponentManager.Listener) listener).afterStart(ComponentManagerImpl.this, isResume);
1136            }
1137        }
1138
1139        public void beforeStop(boolean isStandby) {
1140            for (Object listener : listeners.getListeners()) {
1141                ((ComponentManager.Listener) listener).beforeStop(ComponentManagerImpl.this, isStandby);
1142            }
1143        }
1144
1145        public void afterStop(boolean isStandby) {
1146            for (Object listener : listeners.getListeners()) {
1147                ((ComponentManager.Listener) listener).afterStop(ComponentManagerImpl.this, isStandby);
1148            }
1149        }
1150
1151    }
1152
1153    protected static class Stash {
1154
1155        protected volatile List<RegistrationInfo> toAdd;
1156
1157        protected volatile Set<ComponentName> toRemove;
1158
1159        public Stash() {
1160            toAdd = new ArrayList<>();
1161            toRemove = new HashSet<>();
1162        }
1163
1164        public void add(RegistrationInfo ri) {
1165            this.toAdd.add(ri);
1166        }
1167
1168        public void remove(ComponentName name) {
1169            this.toRemove.add(name);
1170        }
1171
1172        public boolean isEmpty() {
1173            return toAdd.isEmpty() && toRemove.isEmpty();
1174        }
1175
1176        public List<RegistrationInfo> getRegistrationsToRemove(ComponentRegistry reg) {
1177            List<RegistrationInfo> ris = new ArrayList<>();
1178            for (ComponentName name : toRemove) {
1179                RegistrationInfo ri = reg.getComponent(name);
1180                if (ri != null) {
1181                    ris.add(ri);
1182                }
1183            }
1184            return ris;
1185        }
1186
1187    }
1188
1189}