View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  
25  package org.modeshape.common.component;
26  
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.concurrent.CopyOnWriteArrayList;
31  import java.util.concurrent.atomic.AtomicReference;
32  import java.util.concurrent.locks.Lock;
33  import java.util.concurrent.locks.ReentrantLock;
34  import net.jcip.annotations.GuardedBy;
35  import net.jcip.annotations.ThreadSafe;
36  import org.modeshape.common.CommonI18n;
37  import org.modeshape.common.SystemFailureException;
38  import org.modeshape.common.util.CheckArg;
39  import org.modeshape.common.util.Reflection;
40  
41  /**
42   * Maintains the list of component instances for the system. This class does not actively update the component configurations, but
43   * is designed to properly maintain the instances when those configurations are changed by other callers. If the components are
44   * subclasses of {@link Component}, then they will be {@link Component#setConfiguration(ComponentConfig) configured} with the
45   * appropriate configuration.
46   * <p>
47   * Therefore, this library does guarantee that the {@link #getInstances() instances} at the time they are {@link #getInstances()
48   * obtained} are always reflected by the configurations.
49   * </p>
50   * 
51   * @param <ComponentType> the type of component being managed, which may be a subclass of {@link Component}
52   * @param <ConfigType> the configuration type describing the components
53   */
54  @ThreadSafe
55  public class ComponentLibrary<ComponentType, ConfigType extends ComponentConfig> {
56  
57      /**
58       * Class loader factory instance that always returns the {@link Thread#getContextClassLoader() current thread's context class
59       * loader} (if not null) or component library's class loader.
60       */
61      public static final ClassLoaderFactory DEFAULT = new StandardClassLoaderFactory(ComponentLibrary.class.getClassLoader());
62  
63      /**
64       * The class loader factory
65       */
66      private final AtomicReference<ClassLoaderFactory> classLoaderFactory = new AtomicReference<ClassLoaderFactory>(DEFAULT);
67  
68      /**
69       * The list of component instances. The index of each component instance matches the corresponding configuration instance in
70       * {@link #configs}
71       */
72      @GuardedBy( value = "lock" )
73      private final List<ComponentType> instances = new CopyOnWriteArrayList<ComponentType>();
74      private final List<ConfigType> configs = new CopyOnWriteArrayList<ConfigType>();
75      private final List<ComponentType> unmodifiableInstances = Collections.unmodifiableList(instances);
76      private final Lock lock = new ReentrantLock();
77      private final boolean addBeforeExistingConfigs;
78  
79      /**
80       * Create a new library of components.
81       */
82      public ComponentLibrary() {
83          this(false);
84      }
85  
86      /**
87       * Create a new library of components.
88       * 
89       * @param addBeforeExistingConfigs <code>true</code> if configurations should be {@link #add(ComponentConfig) added} before
90       *        previously added configurations.
91       */
92      public ComponentLibrary( boolean addBeforeExistingConfigs ) {
93          this.addBeforeExistingConfigs = addBeforeExistingConfigs;
94      }
95  
96      /**
97       * Get the class loader factory that should be used to load the component classes. Unless changed, the library uses the
98       * {@link #DEFAULT default} class loader factory, which uses the {@link Thread#getContextClassLoader() current thread's
99       * context class loader} if not null or the class loader that loaded the library class.
100      * 
101      * @return the class loader factory; never null
102      * @see #setClassLoaderFactory(ClassLoaderFactory)
103      */
104     public ClassLoaderFactory getClassLoaderFactory() {
105         return this.classLoaderFactory.get();
106     }
107 
108     /**
109      * Set the Maven Repository that should be used to load the component classes. Unless changed, the library uses the
110      * {@link #DEFAULT default} class loader factory, which uses the {@link Thread#getContextClassLoader() current thread's
111      * context class loader} if not null or the class loader that loaded the library class.
112      * 
113      * @param classLoaderFactory the class loader factory reference, or null if the {@link #DEFAULT default class loader factory}
114      *        should be used
115      * @see #getClassLoaderFactory()
116      */
117     public void setClassLoaderFactory( ClassLoaderFactory classLoaderFactory ) {
118         this.classLoaderFactory.set(classLoaderFactory != null ? classLoaderFactory : DEFAULT);
119         refreshInstances();
120     }
121 
122     /**
123      * Add the configuration for a component, or update any existing one that represents the {@link ConfigType#equals(Object) same
124      * configuration}
125      * 
126      * @param config the new configuration
127      * @return true if the component was added, or false if there already was an existing and
128      *         {@link ComponentConfig#hasChanged(ComponentConfig) unchanged} component configuration
129      * @throws IllegalArgumentException if <code>config</code> is null
130      * @see #update(ComponentConfig)
131      * @see #remove(ComponentConfig)
132      */
133     public boolean add( ConfigType config ) {
134         CheckArg.isNotNull(config, "component configuration");
135         try {
136             this.lock.lock();
137             // Find an existing configuration that matches ...
138             int index = findIndexOfMatchingConfiguration(config);
139             if (index >= 0) {
140                 // See if the matching configuration has changed ...
141                 ConfigType existingConfig = this.configs.get(index);
142                 if (existingConfig.hasChanged(config)) {
143                     // It has changed, so we need to replace it ...
144                     this.configs.set(index, config);
145                     this.instances.set(index, newInstance(config));
146                 }
147                 return false;
148             }
149             // Didn't find one, so add it ...
150             if (addBeforeExistingConfigs) {
151                 this.configs.add(0, config);
152                 this.instances.add(0, newInstance(config));
153             } else {
154                 this.configs.add(config);
155                 this.instances.add(newInstance(config));
156             }
157             return true;
158         } finally {
159             this.lock.unlock();
160         }
161     }
162 
163     /**
164      * Get the instance given by the configuration with the supplied name.
165      * 
166      * @param name the configuration name
167      * @return the instance, or null if the configuration doesn't exist
168      */
169     public ComponentType getInstance( String name ) {
170         CheckArg.isNotNull(name, "name");
171         try {
172             this.lock.lock();
173             // Find an existing configuration that matches ...
174             int index = findIndexOfMatchingConfiguration(name);
175             return index >= 0 ? this.instances.get(index) : null;
176         } finally {
177             this.lock.unlock();
178         }
179     }
180 
181     /**
182      * Update the configuration for a component, or add it if there is no {@link ConfigType#equals(Object) matching configuration}
183      * .
184      * 
185      * @param config the updated (or new) configuration
186      * @return true if the component was updated, or false if there already was an existing and
187      *         {@link ComponentConfig#hasChanged(ComponentConfig) unchanged} component configuration
188      * @throws IllegalArgumentException if <code>config</code> is null
189      * @see #add(ComponentConfig)
190      * @see #remove(ComponentConfig)
191      */
192     public boolean update( ConfigType config ) {
193         return add(config);
194     }
195 
196     /**
197      * Remove the configuration for a component.
198      * 
199      * @param config the configuration to be removed
200      * @return true if the component was remove, or false if there was no existing configuration
201      * @throws IllegalArgumentException if <code>config</code> is null
202      * @see #add(ComponentConfig)
203      * @see #update(ComponentConfig)
204      */
205     public boolean remove( ConfigType config ) {
206         CheckArg.isNotNull(config, "component configuration");
207         try {
208             this.lock.lock();
209             // Find an existing configuration that matches ...
210             int index = findIndexOfMatchingConfiguration(config);
211             if (index >= 0) {
212                 // Remove the configuration and the component instance ...
213                 this.configs.remove(index);
214                 this.instances.remove(index);
215                 return true;
216             }
217             return false;
218         } finally {
219             this.lock.unlock();
220         }
221     }
222 
223     /**
224      * Refresh the instances by attempting to re-instantiate each registered configuration.
225      * 
226      * @return true if at least one instance was instantiated, or false if none were
227      */
228     public boolean refreshInstances() {
229         try {
230             this.lock.lock();
231             // Loop through and create new instances for each configuration ...
232             boolean found = false;
233             int index = 0;
234             for (ConfigType config : this.configs) {
235                 ComponentType instance = newInstance(config);
236                 found = found || instance != null;
237                 this.instances.set(index, instance);
238                 ++index;
239             }
240             return found;
241         } finally {
242             this.lock.unlock();
243         }
244     }
245 
246     /**
247      * Return the list of components.
248      * 
249      * @return the unmodifiable list of components; never null
250      */
251     public List<ComponentType> getInstances() {
252         return this.unmodifiableInstances;
253     }
254 
255     /**
256      * Instantiate, configure and return a new component described by the supplied configuration. This method does not manage the
257      * returned instance.
258      * 
259      * @param config the configuration describing the component
260      * @return the new component, or null if the component could not be successfully configured
261      * @throws IllegalArgumentException if the component could not be configured properly
262      */
263     @SuppressWarnings( "unchecked" )
264     protected ComponentType newInstance( ConfigType config ) {
265         String[] classpath = config.getComponentClasspathArray();
266         final ClassLoader classLoader = this.getClassLoaderFactory().getClassLoader(classpath);
267         assert classLoader != null;
268         ComponentType newInstance = null;
269         try {
270             // Don't use ClassLoader.loadClass(String), as it doesn't properly initialize the class
271             // (specifically static initializers may not be called)
272             Class<?> componentClass = Class.forName(config.getComponentClassname(), true, classLoader);
273             newInstance = doCreateInstance(componentClass);
274             if (newInstance instanceof Component) {
275                 ((Component<ConfigType>)newInstance).setConfiguration(config);
276             }
277 
278             if (config.getProperties() != null) {
279                 for (Map.Entry<String, Object> entry : config.getProperties().entrySet()) {
280                     // Set the JavaBean-style property on the RepositorySource instance ...
281                     Reflection reflection = new Reflection(newInstance.getClass());
282                     reflection.invokeSetterMethodOnTarget(entry.getKey(), newInstance, entry.getValue());
283                 }
284             }
285         } catch (Throwable e) {
286             throw new SystemFailureException(e);
287         }
288         if (newInstance instanceof Component && ((Component<ConfigType>)newInstance).getConfiguration() == null) {
289             throw new SystemFailureException(CommonI18n.componentNotConfigured.text(config.getName()));
290         }
291         return newInstance;
292     }
293 
294     /**
295      * Method that instantiates the supplied class. This method can be overridden by subclasses that may need to wrap or adapt the
296      * instance to be a ComponentType.
297      * 
298      * @param componentClass
299      * @return the new ComponentType instance
300      * @throws InstantiationException
301      * @throws IllegalAccessException
302      */
303     @SuppressWarnings( "unchecked" )
304     protected ComponentType doCreateInstance( Class<?> componentClass ) throws InstantiationException, IllegalAccessException {
305         return (ComponentType)componentClass.newInstance();
306     }
307 
308     /**
309      * Find the index for the matching {@link #configs configuration} and {@link #instances component}.
310      * 
311      * @param config the configuration; may not be null
312      * @return the index, or -1 if not found
313      */
314     @GuardedBy( value = "lock" )
315     protected int findIndexOfMatchingConfiguration( ConfigType config ) {
316         // Iterate through the configurations and look for an existing one that matches
317         for (int i = 0, length = this.configs.size(); i != length; i++) {
318             ConfigType existingConfig = this.configs.get(i);
319             assert existingConfig != null;
320             if (existingConfig.equals(config)) {
321                 return i;
322             }
323         }
324         return -1;
325     }
326 
327     /**
328      * Find the index for the matching {@link #configs configuration} and {@link #instances component}.
329      * 
330      * @param name the configuration name; may not be null
331      * @return the index, or -1 if not found
332      */
333     @GuardedBy( value = "lock" )
334     protected int findIndexOfMatchingConfiguration( String name ) {
335         // Iterate through the configurations and look for an existing one that matches
336         for (int i = 0, length = this.configs.size(); i != length; i++) {
337             ConfigType existingConfig = this.configs.get(i);
338             assert existingConfig != null;
339             if (existingConfig.getName().equals(name)) {
340                 return i;
341             }
342         }
343         return -1;
344     }
345 }