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 }