001 /***************************************************************************** 002 * Copyright (C) PicoContainer Organization. All rights reserved. * 003 * ------------------------------------------------------------------------- * 004 * The software in this package is published under the terms of the BSD * 005 * style license a copy of which has been included with this distribution in * 006 * the LICENSE.txt file. * 007 * * 008 * Original code by * 009 *****************************************************************************/ 010 package org.picocontainer.defaults; 011 012 import org.picocontainer.ComponentMonitor; 013 import org.picocontainer.Parameter; 014 import org.picocontainer.PicoContainer; 015 import org.picocontainer.PicoInitializationException; 016 import org.picocontainer.PicoIntrospectionException; 017 018 import java.lang.reflect.Constructor; 019 import java.lang.reflect.InvocationTargetException; 020 import java.lang.reflect.Method; 021 import java.security.AccessController; 022 import java.security.PrivilegedAction; 023 import java.util.ArrayList; 024 import java.util.Collections; 025 import java.util.HashSet; 026 import java.util.List; 027 import java.util.Set; 028 029 /** 030 * Instantiates components using empty constructors and 031 * <a href="http://docs.codehaus.org/display/PICO/Setter+Injection">Setter Injection</a>. 032 * For easy setting of primitive properties, also see {@link BeanPropertyComponentAdapter}. 033 * <p/> 034 * <em> 035 * Note that this class doesn't cache instances. If you want caching, 036 * use a {@link CachingComponentAdapter} around this one. 037 * </em> 038 * </p> 039 * 040 * @author Aslak Hellesøy 041 * @author Jörg Schaible 042 * @author Mauro Talevi 043 * @version $Revision: 2971 $ 044 */ 045 public class SetterInjectionComponentAdapter extends InstantiatingComponentAdapter { 046 private transient Guard instantiationGuard; 047 private transient List setters; 048 private transient List setterNames; 049 private transient Class[] setterTypes; 050 051 /** 052 * Constructs a SetterInjectionComponentAdapter 053 * 054 * @param componentKey the search key for this implementation 055 * @param componentImplementation the concrete implementation 056 * @param parameters the parameters to use for the initialization 057 * @param allowNonPublicClasses flag to allow instantiation of non-public classes. 058 * @param monitor the component monitor used by this adapter 059 * @param lifecycleStrategy the component lifecycle strategy used by this adapter 060 * @throws AssignabilityRegistrationException 061 * if the key is a type and the implementation cannot be assigned to. 062 * @throws NotConcreteRegistrationException 063 * if the implementation is not a concrete class. 064 * @throws NullPointerException if one of the parameters is <code>null</code> 065 */ 066 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor, LifecycleStrategy lifecycleStrategy) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 067 super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor, lifecycleStrategy); 068 } 069 070 071 /** 072 * Constructs a SetterInjectionComponentAdapter 073 * 074 * @param componentKey the search key for this implementation 075 * @param componentImplementation the concrete implementation 076 * @param parameters the parameters to use for the initialization 077 * @param allowNonPublicClasses flag to allow instantiation of non-public classes. 078 * @param monitor the component monitor used by this adapter 079 * @throws AssignabilityRegistrationException 080 * if the key is a type and the implementation cannot be assigned to. 081 * @throws NotConcreteRegistrationException 082 * if the implementation is not a concrete class. 083 * @throws NullPointerException if one of the parameters is <code>null</code> 084 */ 085 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 086 super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor); 087 } 088 089 /** 090 * Constructs a SetterInjectionComponentAdapter with a {@link DelegatingComponentMonitor} as default. 091 * 092 * @param componentKey the search key for this implementation 093 * @param componentImplementation the concrete implementation 094 * @param parameters the parameters to use for the initialization 095 * @param allowNonPublicClasses flag to allow instantiation of non-public classes. 096 * @throws AssignabilityRegistrationException 097 * if the key is a type and the implementation cannot be assigned to. 098 * @throws NotConcreteRegistrationException 099 * if the implementation is not a concrete class. 100 * @throws NullPointerException if one of the parameters is <code>null</code> 101 */ 102 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 103 super(componentKey, componentImplementation, parameters, allowNonPublicClasses); 104 } 105 106 /** 107 * Constructs a SetterInjectionComponentAdapter with key, implementation and parameters. 108 * 109 * @param componentKey the search key for this implementation 110 * @param componentImplementation the concrete implementation 111 * @param parameters the parameters to use for the initialization 112 * @throws AssignabilityRegistrationException 113 * if the key is a type and the implementation cannot be assigned to. 114 * @throws NotConcreteRegistrationException 115 * if the implementation is not a concrete class. 116 * @throws NullPointerException if one of the parameters is <code>null</code> 117 */ 118 public SetterInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 119 this(componentKey, componentImplementation, parameters, false); 120 } 121 122 protected Constructor getGreediestSatisfiableConstructor(PicoContainer container) throws PicoIntrospectionException, UnsatisfiableDependenciesException, AmbiguousComponentResolutionException, AssignabilityRegistrationException, NotConcreteRegistrationException { 123 final Constructor constructor = getConstructor(); 124 getMatchingParameterListForSetters(container); 125 return constructor; 126 } 127 128 private Constructor getConstructor() throws PicoInvocationTargetInitializationException { 129 Object retVal = AccessController.doPrivileged(new PrivilegedAction() { 130 public Object run() { 131 try { 132 return getComponentImplementation().getConstructor((Class[])null); 133 } catch (NoSuchMethodException e) { 134 return new PicoInvocationTargetInitializationException(e); 135 } catch (SecurityException e) { 136 return new PicoInvocationTargetInitializationException(e); 137 } 138 } 139 }); 140 if (retVal instanceof Constructor) { 141 return (Constructor) retVal; 142 } else { 143 throw (PicoInitializationException) retVal; 144 } 145 } 146 147 private Parameter[] getMatchingParameterListForSetters(PicoContainer container) throws PicoInitializationException, UnsatisfiableDependenciesException { 148 if (setters == null) { 149 initializeSetterAndTypeLists(); 150 } 151 152 final List matchingParameterList = new ArrayList(Collections.nCopies(setters.size(), null)); 153 final Set nonMatchingParameterPositions = new HashSet(); 154 final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(setterTypes); 155 for (int i = 0; i < currentParameters.length; i++) { 156 final Parameter parameter = currentParameters[i]; 157 boolean failedDependency = true; 158 for (int j = 0; j < setterTypes.length; j++) { 159 if (matchingParameterList.get(j) == null && parameter.isResolvable(container, this, setterTypes[j])) { 160 matchingParameterList.set(j, parameter); 161 failedDependency = false; 162 break; 163 } 164 } 165 if (failedDependency) { 166 nonMatchingParameterPositions.add(new Integer(i)); 167 } 168 } 169 170 final Set unsatisfiableDependencyTypes = new HashSet(); 171 for (int i = 0; i < matchingParameterList.size(); i++) { 172 if (matchingParameterList.get(i) == null) { 173 unsatisfiableDependencyTypes.add(setterTypes[i]); 174 } 175 } 176 if (unsatisfiableDependencyTypes.size() > 0) { 177 throw new UnsatisfiableDependenciesException(this, unsatisfiableDependencyTypes, container); 178 } else if (nonMatchingParameterPositions.size() > 0) { 179 throw new PicoInitializationException("Following parameters do not match any of the setters for " + getComponentImplementation() + ": " + nonMatchingParameterPositions.toString()); 180 } 181 return (Parameter[]) matchingParameterList.toArray(new Parameter[matchingParameterList.size()]); 182 } 183 184 public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException { 185 final Constructor constructor = getConstructor(); 186 if (instantiationGuard == null) { 187 instantiationGuard = new Guard() { 188 public Object run() { 189 final Parameter[] matchingParameters = getMatchingParameterListForSetters(guardedContainer); 190 ComponentMonitor componentMonitor = currentMonitor(); 191 Object componentInstance; 192 long startTime = System.currentTimeMillis(); 193 try { 194 componentMonitor.instantiating(constructor); 195 componentInstance = newInstance(constructor, null); 196 } catch (InvocationTargetException e) { 197 componentMonitor.instantiationFailed(constructor, e); 198 if (e.getTargetException() instanceof RuntimeException) { 199 throw (RuntimeException) e.getTargetException(); 200 } else if (e.getTargetException() instanceof Error) { 201 throw (Error) e.getTargetException(); 202 } 203 throw new PicoInvocationTargetInitializationException(e.getTargetException()); 204 } catch (InstantiationException e) { 205 // can't get here because checkConcrete() will catch it earlier, but see PICO-191 206 ///CLOVER:OFF 207 componentMonitor.instantiationFailed(constructor, e); 208 throw new PicoInitializationException("Should never get here"); 209 ///CLOVER:ON 210 } catch (IllegalAccessException e) { 211 // can't get here because either filtered or access mode set 212 ///CLOVER:OFF 213 componentMonitor.instantiationFailed(constructor, e); 214 throw new PicoInitializationException(e); 215 ///CLOVER:ON 216 } 217 Method setter = null; 218 Object injected[] = new Object[setters.size()]; 219 try { 220 for (int i = 0; i < setters.size(); i++) { 221 setter = (Method) setters.get(i); 222 componentMonitor.invoking(setter, componentInstance); 223 Object toInject = matchingParameters[i].resolveInstance(guardedContainer, SetterInjectionComponentAdapter.this, setterTypes[i]); 224 setter.invoke(componentInstance, new Object[]{toInject}); 225 injected[i] = toInject; 226 //componentMonitor.invoked(setter, componentInstance, System.currentTimeMillis() - startTime); 227 } 228 componentMonitor.instantiated(constructor, componentInstance, injected, System.currentTimeMillis() - startTime); 229 return componentInstance; 230 } catch (InvocationTargetException e) { 231 //componentMonitor.invocationFailed(setter, componentInstance, e); 232 if (e.getTargetException() instanceof RuntimeException) { 233 throw (RuntimeException) e.getTargetException(); 234 } else if (e.getTargetException() instanceof Error) { 235 throw (Error) e.getTargetException(); 236 } 237 throw new PicoInvocationTargetInitializationException(e.getTargetException()); 238 } catch (IllegalAccessException e) { 239 //componentMonitor.invocationFailed(setter, componentInstance, e); 240 throw new PicoInvocationTargetInitializationException(e); 241 } 242 243 } 244 }; 245 } 246 instantiationGuard.setArguments(container); 247 return instantiationGuard.observe(getComponentImplementation()); 248 } 249 250 public void verify(final PicoContainer container) throws PicoIntrospectionException { 251 if (verifyingGuard == null) { 252 verifyingGuard = new Guard() { 253 public Object run() { 254 final Parameter[] currentParameters = getMatchingParameterListForSetters(guardedContainer); 255 for (int i = 0; i < currentParameters.length; i++) { 256 currentParameters[i].verify(container, SetterInjectionComponentAdapter.this, setterTypes[i]); 257 } 258 return null; 259 } 260 }; 261 } 262 verifyingGuard.setArguments(container); 263 verifyingGuard.observe(getComponentImplementation()); 264 } 265 266 private void initializeSetterAndTypeLists() { 267 setters = new ArrayList(); 268 setterNames = new ArrayList(); 269 final List typeList = new ArrayList(); 270 final Method[] methods = getMethods(); 271 for (int i = 0; i < methods.length; i++) { 272 final Method method = methods[i]; 273 final Class[] parameterTypes = method.getParameterTypes(); 274 // We're only interested if there is only one parameter and the method name is bean-style. 275 if (parameterTypes.length == 1) { 276 String methodName = method.getName(); 277 boolean isBeanStyle = methodName.length() >= 4 && methodName.startsWith("set") && Character.isUpperCase(methodName.charAt(3)); 278 if (isBeanStyle) { 279 String attribute = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); 280 setters.add(method); 281 setterNames.add(attribute); 282 typeList.add(parameterTypes[0]); 283 } 284 } 285 } 286 setterTypes = (Class[]) typeList.toArray(new Class[0]); 287 } 288 289 private Method[] getMethods() { 290 return (Method[]) AccessController.doPrivileged(new PrivilegedAction() { 291 public Object run() { 292 return getComponentImplementation().getMethods(); 293 } 294 }); 295 } 296 }