View Javadoc
1   /*
2    * Copyright 2012 Oracle Corporation. All Rights Reserved.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.fatwire.gst.foundation.controller.action.support;
17  
18  import java.lang.reflect.Constructor;
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.commons.logging.Log;
28  
29  import COM.FutureTense.Interfaces.ICS;
30  
31  import com.fatwire.gst.foundation.controller.action.Factory;
32  import com.fatwire.gst.foundation.controller.annotation.ServiceProducer;
33  import com.fatwire.gst.foundation.facade.logging.LogUtil;
34  
35  /**
36   * Factory making use to reflection ({@link #reflectionStrategy(String, Class)}
37   * and {@link #ctorStrategy(String, Class)}) to produce objects.
38   * <p/>
39   * This class caches the produced objects for the lifetime of this object.
40   * Effectively this means the lifetime of the ICS object.
41   * 
42   * @author Dolf Dijkstra
43   * 
44   */
45  public abstract class BaseFactory implements Factory {
46  
47      protected static final Log LOG = LogUtil.getLog(IcsBackedObjectFactoryTemplate.class);
48  
49      protected final ICS ics;
50  
51      private final Map<String, Object> objectCache = new HashMap<String, Object>();
52      private Factory[] roots = new Factory[0];;
53  
54      public BaseFactory(ICS ics) {
55          super();
56          this.ics = ics;
57  
58      }
59  
60      public BaseFactory(ICS ics, Factory... roots) {
61          super();
62          this.ics = ics;
63          if (roots != null)
64              this.roots = roots;
65      }
66  
67      @Override
68      public final <T> T getObject(final String name, final Class<T> fieldType) {
69  
70          T o;
71          try {
72              o = locate(name, fieldType);
73              if (o == null) {
74                  for (Factory root : roots) {
75                      o = root.getObject(name, fieldType);
76                      if (o != null)
77                          return o;
78  
79                  }
80                  // only try ctor at the root level, otherwise
81                  // it will be invoked on each BaseFactory
82                  if (roots.length == 0)
83                      o = ctorStrategy(name, fieldType);
84              }
85          } catch (InvocationTargetException e) {
86              throw new RuntimeException(e.getTargetException());
87          }
88          return o;
89      }
90  
91      /**
92       * Internal method to check for Services or create Services.
93       * 
94       * @param name
95       * @param c
96       * @return the found service, null if no T can be created.
97       * @throws InvocationTargetException
98       */
99      @SuppressWarnings("unchecked")
100     protected <T> T locate(final String askedName, final Class<T> c) throws InvocationTargetException {
101         if (ICS.class.isAssignableFrom(c)) {
102             return (T) ics;
103         }
104         if (c.isArray()) {
105             throw new IllegalArgumentException("Arrays are not supported");
106         }
107         final String name = StringUtils.isNotBlank(askedName) ? askedName : c.getSimpleName();
108 
109         if (StringUtils.isBlank(name)) {
110             return null;
111         }
112 
113         Object o = objectCache.get(name);
114         if (o != null && !c.isAssignableFrom(o.getClass()))
115             throw new IllegalStateException("Name conflict: '" + name + "' is in cache and is of type  '"
116                     + o.getClass() + "' but a '" + c.getName()
117                     + "' was asked for. Please check your factories for naming conflicts.");
118         if (o == null) {
119             o = namedAnnotationStrategy(name, c);
120         }
121         if (o == null) {
122             o = unnamedAnnotationStrategy(name, c);
123         }
124 
125         if (o == null) {
126             o = reflectionStrategy(name, c);
127         }
128 
129         return (T) o;
130     }
131 
132     /**
133      * Method to find classes to use for the producer methods. This
134      * implementation returns {@link #getClass()}.</p> Subclasses can return and
135      * are encouraged to return other classes.
136      * 
137      * @param ics
138      * @return array of classes to use for reflection
139      */
140     protected Class<?>[] factoryClasses(ICS ics) {
141         return new Class[] { getClass() };
142     }
143 
144     /**
145      * Tries to create the object based on the {@link ServiceProducer}
146      * annotation where the names match.
147      * 
148      * @param name
149      * @param c
150      * @return
151      * @throws InvocationTargetException
152      */
153     protected <T> T namedAnnotationStrategy(String name, Class<T> c) throws InvocationTargetException {
154 
155         for (Class<?> reflectionClass : factoryClasses(ics)) {
156             for (Method m : reflectionClass.getMethods()) {
157                 if (m.isAnnotationPresent(ServiceProducer.class)) {
158                     if (c.isAssignableFrom(m.getReturnType())) {
159                         String n = m.getAnnotation(ServiceProducer.class).name();
160                         if (name.equals(n)) {
161                             return createFromMethod(name, c, m);
162                         }
163                     }
164                 }
165 
166             }
167         }
168         return null;
169     }
170 
171     /**
172      * Tries to create the object based on the {@link ServiceProducer}
173      * annotation without a name.
174      * 
175      * @param name
176      * @param c
177      * @return
178      * @throws InvocationTargetException
179      */
180     protected <T> T unnamedAnnotationStrategy(String name, Class<T> c) throws InvocationTargetException {
181 
182         for (Class<?> reflectionClass : factoryClasses(ics)) {
183             for (Method m : reflectionClass.getMethods()) {
184                 if (m.isAnnotationPresent(ServiceProducer.class)) {
185                     if (c.isAssignableFrom(m.getReturnType())) {
186                         String n = m.getAnnotation(ServiceProducer.class).name();
187                         if (StringUtils.isBlank(n)) {
188                             return createFromMethod(name, c, m);
189                         }
190                     }
191                 }
192 
193             }
194         }
195         return null;
196     }
197 
198     /**
199      * Reflection based producer method.
200      * <p/>
201      * This method uses reflection to find producer methods to the following
202      * rules:
203      * <ul>
204      * <li>public <b>static</b> Foo createFoo(ICS ics, Factory factory){}</li>
205      * <li>public Foo createFoo(ICS ics){}</li>
206      * </ul>
207      * If the non-static version is used the implementing class needs to have a
208      * public constructor that takes {@link ICS} and {@link Factory} as
209      * arguments. To this class the current ICS and this object will be passed.
210      * 
211      * 
212      * @param name the simple name of the object to produce
213      * @param c the class with the type information of the object to produce
214      * @return the created object, null if no producer method was found or when
215      *         that method returned null.
216      * @throws InvocationTargetException when the create&lt;Type&gt; method
217      *             throws an exception.
218      */
219     protected <T> T reflectionStrategy(String name, Class<T> c) throws InvocationTargetException {
220 
221         for (Class<?> reflectionClass : factoryClasses(ics)) {
222 
223             for (Method m : reflectionClass.getMethods()) {
224                 if (m.getName().equals("create" + c.getSimpleName())) {
225                     if (c.isAssignableFrom(m.getReturnType())) {
226                         return createFromMethod(name, c, m);
227                     }
228                 }
229             }
230         }
231         return null;
232     }
233 
234     /**
235      * @param name name of the object
236      * @param c the type of the object to create
237      * @param m the method to use to create the object
238      * @return
239      * @throws InvocationTargetException
240      */
241     @SuppressWarnings("unchecked")
242     protected <T> T createFromMethod(String name, Class<T> c, Method m) throws InvocationTargetException {
243         Object o = null;
244         if (LOG.isTraceEnabled()) {
245             LOG.trace("trying to create a " + c.getName() + " object with name " + name + "  from method "
246                     + m.toGenericString());
247         }
248 
249         if (c.isAssignableFrom(m.getReturnType())) {
250             Object from = null;
251 
252             if (!Modifier.isStatic(m.getModifiers())) {
253                 Class<?> reflectionClass = m.getDeclaringClass();
254 
255                 if (reflectionClass.isAssignableFrom(getClass())) {
256                     from = this; // TODO will this clash if this class and
257                                  // declared class have same parent class?
258                 } else {
259                     Constructor<?> ctor;
260 
261                     try {
262                         ctor = reflectionClass.getConstructor(ICS.class, Factory.class);
263                         if (Modifier.isPublic(ctor.getModifiers())) {
264                             from = ctor.newInstance(ics, this);
265                         } else {
266                             throw new NoSuchMethodExceptionRuntimeException(reflectionClass.getName()
267                                     + " does not have a public (ICS,Factory) constructor.");
268                         }
269                     } catch (NoSuchMethodException e) {
270                         throw new NoSuchMethodExceptionRuntimeException(reflectionClass.getName()
271                                 + " should have a public constructor accepting a ICS and Factory.");
272                     } catch (InstantiationException e) {
273                         LOG.error(e.getMessage());
274                     } catch (IllegalArgumentException e) {
275                         LOG.error("Huh, Can't happen, the arguments are checked: " + m.toString() + ", "
276                                 + e.getMessage());
277                     } catch (IllegalAccessException e) {
278                         LOG.error("Huh, Can't happen, the modifier is checked for public: " + m.toString() + ", "
279                                 + e.getMessage());
280                     }
281                 }
282 
283             }
284             if (m.getParameterTypes().length == 2 && m.getParameterTypes()[0].isAssignableFrom(ICS.class)
285                     && m.getParameterTypes()[1].isAssignableFrom(Factory.class)) {
286                 o = invokeCreateMethod(m, from, name, ics, this);
287             } else if (m.getParameterTypes().length == 1 && m.getParameterTypes()[0].isAssignableFrom(ICS.class)) {
288                 o = invokeCreateMethod(m, from, name, ics);
289             } else if (m.getParameterTypes().length == 0) {
290                 o = invokeCreateMethod(m, from, name);
291             }
292             if (shouldCache(m))
293                 objectCache.put(name, o);
294 
295         }
296         return (T) o;
297     }
298 
299     /**
300      * @param m method in invoke
301      * @param from object to invoke from
302      * @param name the name of the object
303      * @param arguments the arguments to pass to the method
304      * @return
305      * @throws InvocationTargetException
306      */
307     protected Object invokeCreateMethod(Method m, Object from, String name, Object... arguments)
308             throws InvocationTargetException {
309         try {
310             return m.invoke(from, arguments);
311         } catch (IllegalArgumentException e) {
312             LOG.error("Huh, Can't happen, the arguments are checked: " + m.toString() + ", " + e.getMessage());
313         } catch (IllegalAccessException e) {
314             LOG.error("Huh, Can't happen, the modifier is checked for public: " + m.toString() + ", " + e.getMessage());
315         }
316         return null;
317     }
318 
319     protected boolean shouldCache(Method m) {
320         boolean r = false;
321         if (m.isAnnotationPresent(ServiceProducer.class)) {
322             ServiceProducer annon = m.getAnnotation(ServiceProducer.class);
323             r = annon.cache();
324         }
325         return r;
326     }
327 
328     /**
329      * @param e
330      */
331     protected void throwRuntimeException(InvocationTargetException e) {
332         Throwable t = e.getTargetException();
333         if (t == null) {
334             throw new RuntimeException(e);
335         } else if (t instanceof RuntimeException) {
336             throw (RuntimeException) t;
337         } else {
338             throw new RuntimeException(t);
339         }
340     }
341 
342     /**
343      * @param name
344      * @param c
345      * @return
346      * @throws InvocationTargetException
347      */
348     protected <T> T ctorStrategy(final String name, final Class<T> c) throws InvocationTargetException {
349         T o = null;
350         try {
351             if (c.isInterface() || Modifier.isAbstract(c.getModifiers())) {
352                 if (LOG.isDebugEnabled())
353                     LOG.debug("Could not create  a " + c.getName() + " via a Template method. The class '"
354                             + c.getName()
355                             + "' is an interface or abstract class, giving up as a class cannot be constructed.");
356                 return null;
357             }
358 
359             if (LOG.isDebugEnabled())
360                 LOG.debug("Could not create  a " + c.getName() + " via a Template method, trying via constructor.");
361             final Constructor<T> constr = c.getConstructor(ICS.class);
362             o = constr.newInstance(ics);
363         } catch (final NoSuchMethodException e1) {
364             LOG.debug("Could not create  a " + c.getName() + " via a constructor method.");
365         } catch (IllegalArgumentException e) {
366             LOG.debug("Could not create  a " + c.getName() + " via a constructor method.");
367         } catch (InstantiationException e) {
368             LOG.debug("Could not create  a " + c.getName() + " via a constructor method.");
369         } catch (IllegalAccessException e) {
370             LOG.debug("Could not create  a " + c.getName() + " via a constructor method.");
371         }
372         return o;
373     }
374 
375     @ServiceProducer(cache = false)
376     public ICS createICS(final ICS ics) {
377         return ics;
378     }
379 
380     @Override
381     public String toString() {
382         return "BaseFactory [roots=" + Arrays.toString(roots) + "]";
383     }
384 
385 }