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