001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2016, 2022 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.Arrays; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.function.Function; 027import static java.util.function.Predicate.not; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030import org.jgrapes.core.Channel; 031import org.jgrapes.core.Component; 032import org.jgrapes.core.ComponentFactory; 033import org.jgrapes.core.events.Stop; 034import org.jgrapes.util.ComponentProvider; 035import org.jgrapes.util.events.ConfigurationUpdate; 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 * A component that collects all services from the OSGi service registry 043 * which implement the sub-interface of {@link ComponentFactory} 044 * specified when creating the collector. The collector uses each service 045 * found to create one or more {@link Component}s that are then attached 046 * to the component collector instance. 047 * 048 * Effectively, the component collector leverages OSGi's service layer 049 * to modify the component tree at run-time. 050 * 051 * This class uses {@link ComponentProvider#setFactories(ComponentFactory...)} 052 * and {@link ComponentProvider#setPinned(List)} for its implementation. 053 * As it inherits from {@link ComponentProvider}, it automatically 054 * supports the provisioning of additional components through 055 * {@link ConfigurationUpdate} events. If this is not desired, invoke 056 * {@link ComponentProvider#setComponentsEntry(String)} with `null` as 057 * argument. 058 * 059 * @param <F> the component factory type 060 */ 061@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", 062 "PMD.DataflowAnomalyAnalysis" }) 063public class ComponentCollector<F extends ComponentFactory> 064 extends ComponentProvider implements ServiceTrackerCustomizer<F, F> { 065 066 private static final List<Map<Object, Object>> SINGLE_DEFAULT 067 = Arrays.asList(Collections.emptyMap()); 068 069 private BundleContext context; 070 @SuppressWarnings("PMD.SingularField") 071 private ServiceTracker<F, F> serviceTracker; 072 private Function<String, List<Map<Object, Object>>> configurator; 073 074 /** 075 * Creates a collector component that uses a {@link ServiceTracker} 076 * to monitor the addition and removal of component factories. 077 * 078 * @see #addingService(ServiceReference) 079 * @see #removedService(ServiceReference, ComponentFactory) 080 * 081 * @param componentChannel this component's channel 082 * @param context the OSGi {@link BundleContext} 083 * @param factoryCls the factory class 084 * @param configurator the function that provides the pinned configurations 085 */ 086 public ComponentCollector( 087 Channel componentChannel, BundleContext context, 088 Class<F> factoryCls, 089 Function<String, List<Map<Object, Object>>> configurator) { 090 super(componentChannel); 091 this.context = context; 092 this.configurator = configurator; 093 serviceTracker = new ServiceTracker<>(context, factoryCls, this); 094 serviceTracker.open(); 095 } 096 097 /** 098 * Utility constructor that uses each factory to create a single instance, 099 * using an empty map as properties. 100 * 101 * @param componentChannel this component's channel 102 * @param context the bundle context 103 * @param factoryClass the factory class 104 */ 105 public ComponentCollector(Channel componentChannel, BundleContext context, 106 Class<F> factoryClass) { 107 this(componentChannel, context, factoryClass, type -> SINGLE_DEFAULT); 108 } 109 110 /** 111 * Whenever a new factory is added, it is used to create component 112 * instances with this component's channel. First, the `configurator` 113 * passed to the constructor is invoked with the name of the class 114 * of the component to be created as argument. The list of maps 115 * returned is then added to the pinned components 116 * (see {@link ComponentProvider#setPinned(List)}). 117 * 118 * The map return from the `configurator` is automatically augmented 119 * with an entry with key {@link BundleContext}.class and the bundle 120 * context passed to the constructor. 121 * 122 * @see ServiceTrackerCustomizer#addingService(ServiceReference) 123 */ 124 @Override 125 public F addingService(ServiceReference<F> reference) { 126 F factory = context.getService(reference); 127 if (factories().containsKey(factory.componentType().getName())) { 128 // Factory for the new component type is already known. 129 return factory; 130 } 131 132 // Add configuration to provider before adding new factory 133 List<Map<?, ?>> newConfigs = Stream.concat( 134 // filter existing configs to avoid duplicates 135 pinned().stream().filter(not(c -> factory.componentType().getName() 136 .equals(c.get(COMPONENT_TYPE)))), 137 // add new configs 138 configurator.apply( 139 factory.componentType().getName()).stream().map(c -> { 140 // Received may be immutable 141 var newMap = new HashMap<>(c); 142 newMap.put(BundleContext.class, context); 143 if (!c.containsKey(COMPONENT_TYPE)) { 144 newMap.put(COMPONENT_TYPE, 145 factory.componentType().getName()); 146 } 147 return newMap; 148 })) 149 .collect(Collectors.toList()); 150 setPinned(newConfigs); 151 152 // Now add factory 153 ComponentFactory[] updatedFactories 154 = Stream.concat(factories().values().stream(), Stream.of(factory)) 155 .toArray(s -> new ComponentFactory[s]); 156 setFactories(updatedFactories); 157 return factory; 158 } 159 160 /* 161 * (non-Javadoc) 162 * 163 * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService 164 */ 165 @Override 166 public void modifiedService(ServiceReference<F> reference, F service) { 167 // Do nothing. 168 } 169 170 /** 171 * Deletes all child components with the type produced by the factory that 172 * is removed. A (possibly) final {@link Stop} event is send to the 173 * detached subtrees which may be used to deactivate bundles. 174 * 175 * @see ServiceTrackerCustomizer#removedService(ServiceReference, Object) 176 */ 177 @Override 178 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 179 public void removedService(ServiceReference<F> reference, F service) { 180 // Remove factory. 181 ComponentFactory[] updatedFactories = factories().values().stream() 182 .filter(f -> !f.componentType().getName() 183 .equals(service.componentType().getName())) 184 .toArray(s -> new ComponentFactory[s]); 185 setFactories(updatedFactories); 186 187 // Remove configuration 188 List<Map<?, ?>> updatedConfigs = pinned().stream() 189 .filter(not(c -> service.componentType().getName() 190 .equals(c.get(COMPONENT_TYPE)))) 191 .collect(Collectors.toList()); 192 setPinned(updatedConfigs); 193 } 194}