001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016, 2018  Michael N. Lipp
004 *
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.osgi.core;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.ServiceLoader;
028import java.util.function.Function;
029import java.util.stream.StreamSupport;
030import org.jgrapes.core.Channel;
031import org.jgrapes.core.Component;
032import org.jgrapes.core.ComponentFactory;
033import org.jgrapes.core.ComponentType;
034import org.jgrapes.core.Components;
035import org.jgrapes.core.Manager;
036import org.jgrapes.core.events.Stop;
037import org.osgi.framework.BundleContext;
038import org.osgi.framework.ServiceReference;
039import org.osgi.util.tracker.ServiceTracker;
040import org.osgi.util.tracker.ServiceTrackerCustomizer;
041
042/**
043 * An advanced version of the basic {@link ComponentCollector} that is based on
044 * the OSGi registry and not on the {@link ServiceLoader}.
045 */
046public class ComponentCollector<F extends ComponentFactory> extends Component
047        implements ServiceTrackerCustomizer<F, F> {
048
049    private static final List<Map<Object, Object>> SINGLE_DEFAULT
050        = Arrays.asList(Collections.emptyMap());
051
052    private BundleContext context;
053    @SuppressWarnings("PMD.SingularField")
054    private ServiceTracker<F, F> serviceTracker;
055    private Function<String, List<Map<Object, Object>>> matcher;
056
057    /**
058     * Creates a collector component that uses the {@link ServiceTracker} to
059     * monitor the addition and removal of component factories.
060     *
061     * @param componentChannel this component's channel
062     * @param context the OSGi {@link BundleContext}
063     * @param factoryCls the factory class
064     * @param matcher the matcher function
065     */
066    public ComponentCollector(
067            Channel componentChannel, BundleContext context,
068            Class<F> factoryCls,
069            Function<String, List<Map<Object, Object>>> matcher) {
070        super(componentChannel);
071        this.context = context;
072        this.matcher = matcher;
073        serviceTracker = new ServiceTracker<>(context, factoryCls, this);
074        serviceTracker.open();
075    }
076
077    /**
078     * Utility constructor that uses each factory to create a single instance,
079     * using an empty map as properties.
080     *
081     * @param componentChannel this component's channel
082     * @param context the bundle context
083     * @param factoryClass the factory class
084     */
085    public ComponentCollector(Channel componentChannel, BundleContext context,
086            Class<F> factoryClass) {
087        this(componentChannel, context, factoryClass, type -> SINGLE_DEFAULT);
088    }
089
090    /**
091     * Whenever a new factory is added, it is used to create component instances
092     * with this component's channel. First, the `matcher` function passed to
093     * the constructor is invoked with the name of the class of the component to
094     * be created as argument. The list of maps returned is used to create
095     * components, passing each element in the list as parameter to
096     * {@link ComponentFactory#create(Channel, Map)}. The map return from the
097     * `matcher` is automatically augmented with an entry with key
098     * {@link BundleContext}.class and the bundle context passed to the
099     * constructor.
100     *
101     * @see ServiceTrackerCustomizer#addingService(ServiceReference)
102     */
103    @Override
104    public F addingService(ServiceReference<F> reference) {
105        F factory = context.getService(reference);
106        // Iterate over all nodes of the subtree that has this
107        // component as root.
108        if (StreamSupport.stream(spliterator(), false)
109            .filter(cls -> cls.getClass().equals(factory.componentType()))
110            .count() == 0) {
111            List<Map<Object, Object>> configs = matcher.apply(
112                factory.componentType().getName());
113            for (Map<?, ?> config : configs) {
114                @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops",
115                    "PMD.UseConcurrentHashMap" })
116                Map<Object, Object> props = new HashMap<>(config);
117                props.put(BundleContext.class, context);
118                factory.create(channel(), props).ifPresent(
119                    component -> attach(component));
120            }
121        }
122        return factory;
123    }
124
125    /*
126     * (non-Javadoc)
127     * 
128     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService
129     */
130    @Override
131    public void modifiedService(ServiceReference<F> reference, F service) {
132        // Do nothing.
133    }
134
135    /**
136     * Removes all child component with the type produced by the factory that is
137     * removed. A (possibly) final {@link Stop} event is send to the
138     * detached subtrees which may be used to deactivate bundles.
139     * 
140     * @see ServiceTrackerCustomizer#removedService(ServiceReference, Object)
141     */
142    @Override
143    public void removedService(ServiceReference<F> reference, F service) {
144        // Avoid ConcurrentModificationException
145        List<ComponentType> toBeRemoved = new ArrayList<>();
146        for (ComponentType child : this) {
147            if (child.getClass().equals(service.componentType())) {
148                toBeRemoved.add(child);
149            }
150        }
151        for (ComponentType item : toBeRemoved) {
152            Manager mgr = Components.manager(item);
153            mgr.detach();
154            mgr.activeEventPipeline().fire(new Stop());
155        }
156    }
157}