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 }