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;
030
031import org.jgrapes.core.Channel;
032import org.jgrapes.core.Component;
033import org.jgrapes.core.ComponentFactory;
034import org.jgrapes.core.ComponentType;
035import org.jgrapes.core.Components;
036import org.osgi.framework.BundleContext;
037import org.osgi.framework.ServiceReference;
038import org.osgi.util.tracker.ServiceTracker;
039import org.osgi.util.tracker.ServiceTrackerCustomizer;
040
041/**
042 * An advanced version of the basic {@link ComponentCollector} that is based on
043 * the OSGi registry and not on the {@link ServiceLoader}.
044 */
045public class ComponentCollector<F extends ComponentFactory> extends Component
046        implements ServiceTrackerCustomizer<F, F> {
047
048    private static final List<Map<Object, Object>> SINGLE_DEFAULT
049        = Arrays.asList(Collections.emptyMap());
050
051    private BundleContext context;
052    @SuppressWarnings("PMD.SingularField")
053    private ServiceTracker<F, F> serviceTracker;
054    private Function<String, List<Map<Object, Object>>> matcher;
055
056    /**
057     * Creates a collector component that uses the {@link ServiceTracker} to
058     * monitor the addition and removal of component factories.
059     *
060     * @param componentChannel this component's channel
061     * @param context the OSGi {@link BundleContext}
062     * @param factoryCls the factory class
063     * @param matcher the matcher function
064     */
065    public ComponentCollector(
066            Channel componentChannel, BundleContext context,
067            Class<F> factoryCls,
068            Function<String, List<Map<Object, Object>>> matcher) {
069        super(componentChannel);
070        this.context = context;
071        this.matcher = matcher;
072        serviceTracker = new ServiceTracker<>(context, factoryCls, this);
073        serviceTracker.open();
074    }
075
076    /**
077     * Utility constructor that uses each factory to create a single instance,
078     * using an empty map as properties.
079     * 
080     * @param factoryClass the factory class
081     * @param componentChannel this component's channel
082     */
083    public ComponentCollector(Channel componentChannel, BundleContext context,
084            Class<F> factoryClass) {
085        this(componentChannel, context, factoryClass, type -> SINGLE_DEFAULT);
086    }
087
088    /**
089     * Whenever a new factory is added, it is used to create component instances
090     * with this component's channel. First, the `matcher` function passed to
091     * the constructor is invoked with the name of the class of the component to
092     * be created as argument. The list of maps returned is used to create
093     * components, passing each element in the list as parameter to
094     * {@link ComponentFactory#create(Channel, Map)}. The map return from the
095     * `matcher` is automatically augmented with an entry with key
096     * {@link BundleContext}.class and the bundle context passed to the
097     * constructor.
098     *
099     * @see ServiceTrackerCustomizer#addingService(ServiceReference)
100     */
101    @Override
102    public F addingService(ServiceReference<F> reference) {
103        F factory = context.getService(reference);
104        // Iterate over all nodes of the subtree that has this
105        // component as root.
106        if (StreamSupport.stream(spliterator(), false)
107            .filter(cls -> cls.getClass().equals(factory.componentType()))
108            .count() == 0) {
109            List<Map<Object, Object>> configs = matcher.apply(
110                factory.componentType().getName());
111            for (Map<?, ?> config : configs) {
112                @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops",
113                    "PMD.UseConcurrentHashMap" })
114                Map<Object, Object> props = new HashMap<>(config);
115                props.put(BundleContext.class, context);
116                factory.create(channel(), props).ifPresent(
117                    component -> attach(component));
118            }
119        }
120        return factory;
121    }
122
123    /*
124     * (non-Javadoc)
125     * 
126     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService
127     */
128    @Override
129    public void modifiedService(ServiceReference<F> reference, F service) {
130        // Do nothing.
131    }
132
133    /**
134     * Removes all child component with the type produced by the factory that is
135     * removed.
136     * 
137     * @see ServiceTrackerCustomizer#removedService(ServiceReference, Object)
138     */
139    @Override
140    public void removedService(ServiceReference<F> reference, F service) {
141        // Avoid ConcurrentModificationException
142        List<ComponentType> toBeRemoved = new ArrayList<>();
143        for (ComponentType child : this) {
144            if (child.getClass().equals(service.componentType())) {
145                toBeRemoved.add(child);
146            }
147        }
148        for (ComponentType item : toBeRemoved) {
149            Components.manager(item).detach();
150        }
151    }
152}