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    }