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