001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024
025 package org.jboss.dna.common.component;
026
027 import java.util.Collections;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.concurrent.CopyOnWriteArrayList;
031 import java.util.concurrent.atomic.AtomicReference;
032 import java.util.concurrent.locks.Lock;
033 import java.util.concurrent.locks.ReentrantLock;
034 import net.jcip.annotations.GuardedBy;
035 import net.jcip.annotations.ThreadSafe;
036 import org.jboss.dna.common.CommonI18n;
037 import org.jboss.dna.common.SystemFailureException;
038 import org.jboss.dna.common.util.CheckArg;
039 import org.jboss.dna.common.util.Reflection;
040
041 /**
042 * Maintains the list of component instances for the system. This class does not actively update the component configurations, but
043 * is designed to properly maintain the sequencer instances when those configurations are changed by other callers. If the
044 * components are subclasses of {@link Component}, then they will be {@link Component#setConfiguration(ComponentConfig)
045 * configured} with the appropriate configuration.
046 * <p>
047 * Therefore, this library does guarantee that the {@link #getInstances() instances} at the time they are {@link #getInstances()
048 * obtained} are always reflected by the configurations.
049 * </p>
050 *
051 * @author Randall Hauch
052 * @param <ComponentType> the type of component being managed, which may be a subclass of {@link Component}
053 * @param <ConfigType> the configuration type describing the components
054 */
055 @ThreadSafe
056 public class ComponentLibrary<ComponentType, ConfigType extends ComponentConfig> {
057
058 /**
059 * Class loader factory instance that always returns the {@link Thread#getContextClassLoader() current thread's context class
060 * loader} (if not null) or component library's class loader.
061 */
062 public static final ClassLoaderFactory DEFAULT = new StandardClassLoaderFactory(ComponentLibrary.class.getClassLoader());
063
064 /**
065 * The class loader factory
066 */
067 private final AtomicReference<ClassLoaderFactory> classLoaderFactory = new AtomicReference<ClassLoaderFactory>(DEFAULT);
068
069 /**
070 * The list of component instances. The index of each component instance matches the corresponding configuration instance in
071 * {@link #configs}
072 */
073 @GuardedBy( value = "lock" )
074 private final List<ComponentType> instances = new CopyOnWriteArrayList<ComponentType>();
075 private final List<ConfigType> configs = new CopyOnWriteArrayList<ConfigType>();
076 private final List<ComponentType> unmodifiableInstances = Collections.unmodifiableList(instances);
077 private final Lock lock = new ReentrantLock();
078 private final boolean addBeforeExistingConfigs;
079
080 /**
081 * Create a new library of components.
082 */
083 public ComponentLibrary() {
084 this(false);
085 }
086
087 /**
088 * Create a new library of components.
089 *
090 * @param addBeforeExistingConfigs <code>true</code> if configurations should be {@link #add(ComponentConfig) added} before
091 * previously added configurations.
092 */
093 public ComponentLibrary( boolean addBeforeExistingConfigs ) {
094 this.addBeforeExistingConfigs = addBeforeExistingConfigs;
095 }
096
097 /**
098 * Get the class loader factory that should be used to load the component classes. Unless changed, the library uses the
099 * {@link #DEFAULT default} class loader factory, which uses the {@link Thread#getContextClassLoader() current thread's
100 * context class loader} if not null or the class loader that loaded the library class.
101 *
102 * @return the class loader factory; never null
103 * @see #setClassLoaderFactory(ClassLoaderFactory)
104 */
105 public ClassLoaderFactory getClassLoaderFactory() {
106 return this.classLoaderFactory.get();
107 }
108
109 /**
110 * Set the Maven Repository that should be used to load the sequencer classes. Unless changed, the library uses the
111 * {@link #DEFAULT default} class loader factory, which uses the {@link Thread#getContextClassLoader() current thread's
112 * context class loader} if not null or the class loader that loaded the library class.
113 *
114 * @param classLoaderFactory the class loader factory reference, or null if the {@link #DEFAULT default class loader factory}
115 * should be used
116 * @see #getClassLoaderFactory()
117 */
118 public void setClassLoaderFactory( ClassLoaderFactory classLoaderFactory ) {
119 this.classLoaderFactory.set(classLoaderFactory != null ? classLoaderFactory : DEFAULT);
120 refreshInstances();
121 }
122
123 /**
124 * Add the configuration for a sequencer, or update any existing one that represents the {@link ConfigType#equals(Object) same
125 * configuration}
126 *
127 * @param config the new configuration
128 * @return true if the component was added, or false if there already was an existing and
129 * {@link ComponentConfig#hasChanged(ComponentConfig) unchanged} component configuration
130 * @throws IllegalArgumentException if <code>config</code> is null
131 * @see #update(ComponentConfig)
132 * @see #remove(ComponentConfig)
133 */
134 public boolean add( ConfigType config ) {
135 CheckArg.isNotNull(config, "component configuration");
136 try {
137 this.lock.lock();
138 // Find an existing configuration that matches ...
139 int index = findIndexOfMatchingConfiguration(config);
140 if (index >= 0) {
141 // See if the matching configuration has changed ...
142 ConfigType existingConfig = this.configs.get(index);
143 if (existingConfig.hasChanged(config)) {
144 // It has changed, so we need to replace it ...
145 this.configs.set(index, config);
146 this.instances.set(index, newInstance(config));
147 }
148 return false;
149 }
150 // Didn't find one, so add it ...
151 if (addBeforeExistingConfigs) {
152 this.configs.add(0, config);
153 this.instances.add(0, newInstance(config));
154 } else {
155 this.configs.add(config);
156 this.instances.add(newInstance(config));
157 }
158 return true;
159 } finally {
160 this.lock.unlock();
161 }
162 }
163
164 /**
165 * Update the configuration for a sequencer, or add it if there is no {@link ConfigType#equals(Object) matching configuration}
166 * .
167 *
168 * @param config the updated (or new) configuration
169 * @return true if the component was updated, or false if there already was an existing and
170 * {@link ComponentConfig#hasChanged(ComponentConfig) unchanged} component configuration
171 * @throws IllegalArgumentException if <code>config</code> is null
172 * @see #add(ComponentConfig)
173 * @see #remove(ComponentConfig)
174 */
175 public boolean update( ConfigType config ) {
176 return add(config);
177 }
178
179 /**
180 * Remove the configuration for a sequencer.
181 *
182 * @param config the configuration to be removed
183 * @return true if the component was remove, or false if there was no existing configuration
184 * @throws IllegalArgumentException if <code>config</code> is null
185 * @see #add(ComponentConfig)
186 * @see #update(ComponentConfig)
187 */
188 public boolean remove( ConfigType config ) {
189 CheckArg.isNotNull(config, "component configuration");
190 try {
191 this.lock.lock();
192 // Find an existing configuration that matches ...
193 int index = findIndexOfMatchingConfiguration(config);
194 if (index >= 0) {
195 // Remove the configuration and the sequencer instance ...
196 this.configs.remove(index);
197 this.instances.remove(index);
198 return true;
199 }
200 return false;
201 } finally {
202 this.lock.unlock();
203 }
204 }
205
206 /**
207 * Refresh the instances by attempting to re-instantiate each registered configuration.
208 *
209 * @return true if at least one instance was instantiated, or false if none were
210 */
211 public boolean refreshInstances() {
212 try {
213 this.lock.lock();
214 // Loop through and create new instances for each configuration ...
215 boolean found = false;
216 int index = 0;
217 for (ConfigType config : this.configs) {
218 ComponentType instance = newInstance(config);
219 found = found || instance != null;
220 this.instances.set(index, instance);
221 ++index;
222 }
223 return found;
224 } finally {
225 this.lock.unlock();
226 }
227 }
228
229 /**
230 * Return the list of sequencers.
231 *
232 * @return the unmodifiable list of sequencers; never null
233 */
234 public List<ComponentType> getInstances() {
235 return this.unmodifiableInstances;
236 }
237
238 /**
239 * Instantiate, configure and return a new sequencer described by the supplied configuration. This method does not manage the
240 * returned instance.
241 *
242 * @param config the configuration describing the sequencer
243 * @return the new sequencer, or null if the sequencer could not be successfully configured
244 * @throws IllegalArgumentException if the sequencer could not be configured properly
245 */
246 @SuppressWarnings( "unchecked" )
247 protected ComponentType newInstance( ConfigType config ) {
248 String[] classpath = config.getComponentClasspathArray();
249 final ClassLoader classLoader = this.getClassLoaderFactory().getClassLoader(classpath);
250 assert classLoader != null;
251 ComponentType newInstance = null;
252 try {
253 // Don't use ClassLoader.loadClass(String), as it doesn't properly initialize the class
254 // (specifically static initializers may not be called)
255 Class<?> componentClass = Class.forName(config.getComponentClassname(), true, classLoader);
256 newInstance = doCreateInstance(componentClass);
257 if (newInstance instanceof Component) {
258 ((Component<ConfigType>)newInstance).setConfiguration(config);
259 }
260
261 if (config.getProperties() != null) {
262 for (Map.Entry<String, Object> entry : config.getProperties().entrySet()) {
263 // Set the JavaBean-style property on the RepositorySource instance ...
264 Reflection reflection = new Reflection(newInstance.getClass());
265 reflection.invokeSetterMethodOnTarget(entry.getKey(), newInstance, entry.getValue());
266 }
267 }
268 } catch (Throwable e) {
269 throw new SystemFailureException(e);
270 }
271 if (newInstance instanceof Component && ((Component<ConfigType>)newInstance).getConfiguration() == null) {
272 throw new SystemFailureException(CommonI18n.componentNotConfigured.text(config.getName()));
273 }
274 return newInstance;
275 }
276
277 /**
278 * Method that instantiates the supplied class. This method can be overridden by subclasses that may need to wrap or adapt the
279 * instance to be a ComponentType.
280 *
281 * @param componentClass
282 * @return the new ComponentType instance
283 * @throws InstantiationException
284 * @throws IllegalAccessException
285 */
286 @SuppressWarnings( "unchecked" )
287 protected ComponentType doCreateInstance( Class<?> componentClass ) throws InstantiationException, IllegalAccessException {
288 return (ComponentType)componentClass.newInstance();
289 }
290
291 /**
292 * Find the index for the matching {@link #configs configuration} and {@link #instances sequencer}.
293 *
294 * @param config the configuration; may not be null
295 * @return the index, or -1 if not found
296 */
297 @GuardedBy( value = "lock" )
298 protected int findIndexOfMatchingConfiguration( ConfigType config ) {
299 // Iterate through the configurations and look for an existing one that matches
300 for (int i = 0, length = this.configs.size(); i != length; i++) {
301 ConfigType existingConfig = this.configs.get(i);
302 assert existingConfig != null;
303 if (existingConfig.equals(config)) {
304 return i;
305 }
306 }
307 return -1;
308 }
309
310 }