1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.backsource.utils.lang;
21 import java.util.Enumeration;
22 import java.util.List;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Properties;
26 import java.io.BufferedReader;
27 import java.io.InputStreamReader;
28 import java.io.IOException;
29 import java.net.URL;
30
31 import org.apache.log4j.Logger;
32 /***
33 * A baseclass or helper to make it easier to create factories following the
34 * JAXP pattern; but with more configuration options.
35 *
36 *<p>The normal pattern is to have an abstract factory through which an implementing factory is obtainable through the newInstance() method. The implementing factory then has a method implementation to get the class the factory is meant to
37 * be able to produce. This class helps in defining the behaviour that is used by the abstract factory to lookup the implementing factory.</p>
38 * <p>Factories that uses this class to implement its factory handling must either inherit from this class or demand that the implementing factories have default constructors with public access.</p>
39 *
40 * @author <a href="mailto:pra@tim.se">Peter Antman</a>
41 * @version $Revision: 1.1.1.1 $
42 */
43
44 public class Factory {
45 private static final Logger log = Logger.getLogger(Factory.class);
46
47 public Factory() {
48
49 }
50
51 /***
52 * Find and instantiate an implementing factory.
53 *
54 * The following search order is used:
55 * <ul>
56 <li>Check if env is null and contains key.</li>
57 <li>if reverseOrder is true first use the Service API and look for the file
58 META-INF/services/key where key is the property name and check the leaf classloader first for the resource; then check the system propery named byte key.</lI>
59 <li>if reverseOrder is false, check the system property firt and then use the Service API and return the resource from the highest parent classloader that it is visable from.</li>
60 <li>Last use the default.</li>
61 </ul>
62
63 <p>The reverse order mechanism makes it possible to specify component specific factory configurations in environments which deploys components int their owne classloader.</p>
64
65 * @param key the property name of the factory; the normal pattern is to use the fully qualified classname of the abstract factory.
66 * @param env a possibly null Properties that contains a locally given value for key.
67 * @param defaultClass the default class to load if no configuration is found.
68 * @param reverseOrder in reverse order the service API will be used before any System properties are checked; and the resource file will be searched in leaf class loader order first.
69 * @throws ClassNotFoundException if not default or property was found to create the factory or if the configured factory was not available in the classpath.
70
71 * @throws IllegalArgumentException thrown if key is null.
72 * @throws IllegalStateException if no configuration was found and defaultClass was also null.
73
74 */
75 public static Object findInstance(String key, Properties env,String defaultClass, boolean reverseOrder) throws ClassNotFoundException{
76 String clazz = null;
77 if ( key == null) {
78 throw new IllegalArgumentException("The key used to lookup the factory is not allowed to be null");
79 }
80
81
82 if ( env != null) {
83 clazz = getProperty( env.getProperty(key) );
84 }
85
86
87
88
89
90
91 if ( clazz ==null) {
92 if ( reverseOrder) {
93 clazz = getProperty( jarService(key,reverseOrder) );
94 if ( clazz == null) {
95 clazz = getProperty( System.getProperty(key) );
96 }
97
98 } else {
99 clazz = getProperty( System.getProperty(key) );
100 if ( clazz == null) {
101 clazz = getProperty( jarService(key,reverseOrder) );
102 }
103 }
104 }
105
106
107 if ( clazz == null) {
108 clazz = defaultClass;
109 }
110
111 if ( clazz == null) {
112 throw new IllegalStateException("No valid configuration of " + key + " was found");
113 }
114
115 return newInstance(clazz);
116
117 }
118
119 /***
120 * The property is not null only if it is not null, and has a length of > 0 after its been trimed;
121 */
122 public static String getProperty(String prop) {
123 if ( prop != null) {
124 prop = prop.trim();
125 if ( prop.length() == 0) {
126 prop = null;
127 }
128
129 }
130 return prop;
131 }
132
133
134 /***
135 * Load the given classname from the thread context classloader, or Class.forName if no context loader was found.
136 * @param className the fully qualified name of the class to load.
137 * @throws ClassNotFoundException if it was not possible to load the given class or if it was null.
138 */
139 public static Object newInstance(String className)
140 throws ClassNotFoundException{
141 if ( className == null) {
142 throw new ClassNotFoundException("The name of the class to load is not allowed to be null");
143 }
144
145
146 Class fac = null;
147 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
148 if (classLoader == null) {
149 log.debug("newInstance: 'Thread.currentThread().getContextClassLoader()' returns null!");
150 fac = Class.forName(className);
151 }else {
152 fac =classLoader.loadClass(className);
153 }
154
155 if ( fac == null) {
156 throw new ClassNotFoundException("Could not load " + className + " classloader returned null");
157 }
158 Object o = null;
159 try {
160 o = fac.newInstance();
161 } catch (Exception e) {
162
163 throw new ClassNotFoundException("Could not load class " + className,e);
164 }
165
166 return o;
167 }
168
169 /***
170 * Lookup the resource META-INF/services/key where key is the name of the property file to look for.
171 * <p>The context classloader is searched first and if the service property was not found the classloader of this class is used.</p>
172 * @param key the name of the resource/property.
173 * @param reverseOrder if true return the last resource in the enumeration returned by getResource.
174 * @return the value of the service resource or null of not found.
175 */
176 public static String jarService(String key, boolean reverseOrder) {
177 String name = jarService( Thread.currentThread().getContextClassLoader(), key, reverseOrder);
178 if ( name == null) {
179 log.debug("Key " + key + " not found in context classloader, using std classloader");
180 name = jarService( Factory.class.getClassLoader(), key, reverseOrder);
181 }
182 return name;
183 }
184
185 /***
186 * Lookup the resource META-INF/services/key where key is the name of the property file to look for.
187 * @param cl the classloader to use to lookup the resource.
188 * @param key the name of the resource/property.
189 * @param reverseOrder if true return the last resource in the enumeration returned by getResource.
190 * @return the value of the service resource or null of not found.
191 */
192 public static String jarService(ClassLoader cl, String key, boolean reverseOrder) {
193 BufferedReader rd = null;
194 String factoryClassName = null;
195 try {
196
197 log.debug("META-INF/services/" + key+"="+cl.getResource("META-INF/services/" + key));
198
199 Enumeration res = cl.getResources("META-INF/services/" + key);
200 URL url = null;
201 if ( res != null) {
202 List l = list(res);
203 if ( log.isDebugEnabled()) {
204 log.debug("List of resources found " + l);
205 }
206
207 if ( reverseOrder) {
208 Collections.reverse(l);
209 if ( log.isDebugEnabled()) {
210 log.debug("Reverse list of resources found " + l);
211 }
212
213 }
214 if ( l.size() >= 1) {
215 url = (URL)l.get(0);
216 }
217
218 }
219 if ( url == null) {
220 return null;
221 }
222
223
224
225
226
227 rd = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8") );
228 factoryClassName = rd.readLine();
229 } catch (IOException e) {
230 ;
231 }finally {
232 if ( rd != null) {
233 try {
234 rd.close();
235 } catch (IOException ignore) {
236 ;
237 }
238
239 }
240
241 }
242 return factoryClassName;
243 }
244
245 /***
246 * Implement the jdk 1.4.1 Collection list behaviour.
247 */
248 public static List list(Enumeration e) {
249 ArrayList l = new ArrayList();
250 while (e.hasMoreElements()) {
251 l.add(e.nextElement());
252 }
253 return l;
254 }
255
256 }