1 /*
2 * Copyright (c) 2010 Carman Consulting, Inc.
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
17 package org.metastopheles.annotation;
18
19 import org.metastopheles.BeanMetaData;
20 import org.metastopheles.BeanMetaDataFactory;
21 import org.metastopheles.MetaDataDecorator;
22 import org.metastopheles.MetaDataObject;
23 import org.metastopheles.MethodMetaData;
24 import org.metastopheles.PropertyMetaData;
25 import org.scannotation.AnnotationDB;
26 import org.scannotation.ClasspathUrlFinder;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 import java.io.IOException;
31 import java.lang.annotation.Annotation;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.Method;
34 import java.lang.reflect.Modifier;
35 import java.net.URL;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TreeMap;
40
41 public class AnnotationBeanMetaDataFactory extends BeanMetaDataFactory
42 {
43 //**********************************************************************************************************************
44 // Fields
45 //**********************************************************************************************************************
46
47 private final Logger logger = LoggerFactory.getLogger(getClass());
48
49 //**********************************************************************************************************************
50 // Constructors
51 //**********************************************************************************************************************
52
53 public AnnotationBeanMetaDataFactory()
54 {
55 this(ClasspathUrlFinder.findClassPaths());
56 }
57
58 public AnnotationBeanMetaDataFactory(Class baseClass)
59 {
60 this(ClasspathUrlFinder.findResourceBases(baseClass.getName().replace('.', '/') + ".class", baseClass.getClassLoader() == null ? ClassLoader.getSystemClassLoader() : baseClass.getClassLoader()));
61 }
62
63 public AnnotationBeanMetaDataFactory(URL... urls)
64 {
65 AnnotationDB db = new AnnotationDB();
66 try
67 {
68 db.setScanClassAnnotations(false);
69 db.setScanMethodAnnotations(true);
70 db.setScanFieldAnnotations(false);
71 db.setScanParameterAnnotations(false);
72 db.crossReferenceMetaAnnotations();
73 db.scanArchives(urls);
74 // db.crossReferenceImplementedInterfaces();
75 final Map<String, Set<String>> index = db.getAnnotationIndex();
76 Map<String, Object> targets = new TreeMap<String, Object>();
77
78 final Set<String> manuallyScannedClasses = index.get(ScanMe.class.getName());
79 if (manuallyScannedClasses != null && !manuallyScannedClasses.isEmpty())
80 {
81 for (String className : manuallyScannedClasses)
82 {
83 try
84 {
85 Class c = Class.forName(className);
86 while (c != null)
87 {
88 for (String annotationName : index.keySet())
89 {
90 Set<String> annotatedClasses = index.get(annotationName);
91 if (annotatedClasses.contains(c.getName()))
92 {
93 annotatedClasses.add(className);
94 }
95 }
96 c = c.getSuperclass();
97 }
98 }
99 catch (ClassNotFoundException e)
100 {
101 logger.error("Unable to load manually scanned class " + className + ".", e);
102 }
103
104 }
105 }
106 scanForMetaDataMethods(index, targets, BeanMetaData.class, BeanDecorator.class, getBeanMetaDataDecorators());
107 scanForMetaDataMethods(index, targets, PropertyMetaData.class, PropertyDecorator.class, getPropertyMetaDataDecorators());
108 scanForMetaDataMethods(index, targets, MethodMetaData.class, MethodDecorator.class, getMethodMetaDataDecorators());
109 }
110 catch (IOException e)
111 {
112 throw new RuntimeException("Unable to scan classpath for annotations.", e);
113 }
114 catch (AnnotationDB.CrossReferenceException e)
115 {
116 throw new RuntimeException("Unable to scan classpath for annotations.", e);
117 }
118 }
119
120 //**********************************************************************************************************************
121 // Other Methods
122 //**********************************************************************************************************************
123
124 @SuppressWarnings("unchecked")
125 private <T extends MetaDataObject> void scanForMetaDataMethods(Map<String, Set<String>> index, Map<String, Object> targets, Class<T> metaDataType, Class<? extends Annotation> markerAnnotationType, List<MetaDataDecorator<T>> decorators)
126 {
127 final Set<String> classes = index.get(markerAnnotationType.getName());
128 if (classes == null || classes.isEmpty())
129 {
130 return;
131 }
132
133 for (String className : classes)
134 {
135 try
136 {
137 Class<?> c = Class.forName(className);
138 for (Method method : c.getMethods())
139 {
140 if (method.isAnnotationPresent(markerAnnotationType))
141 {
142 Class[] parameterTypes = method.getParameterTypes();
143 if (parameterTypes.length == 2 && metaDataType.equals(parameterTypes[0]) && parameterTypes[1].isAnnotation())
144 {
145 Class<? extends Annotation> annotationType = (Class<? extends Annotation>) parameterTypes[1];
146 if (Modifier.isStatic(method.getModifiers()))
147 {
148 logger.debug("Adding static decorator method " + method);
149 decorators.add(new MethodBasedDecorator(annotationType, null, method));
150 }
151 else if (!Modifier.isAbstract(c.getModifiers()))
152 {
153 logger.debug("Adding decorator method " + method);
154 Object target = targets.get(c.getName());
155 if (target == null)
156 {
157 logger.debug("Instantiating " + c.getName() + " instance to handle decorator methods found...");
158 target = c.newInstance();
159 targets.put(c.getName(), target);
160 }
161
162 decorators.add(new MethodBasedDecorator(annotationType, target, method));
163 }
164 }
165 }
166 }
167 }
168 catch (ClassNotFoundException e)
169 {
170 logger.error("Unable to load class " + className + ", skipping annotations.", e);
171 }
172 catch (InstantiationException e)
173 {
174 logger.error("Unable to instantiate object of type " + className + ".", e);
175 }
176 catch (IllegalAccessException e)
177 {
178 logger.error("Unable to instantiate object of type " + className + ".", e);
179 }
180 }
181 }
182
183 //**********************************************************************************************************************
184 // Inner Classes
185 //**********************************************************************************************************************
186
187 private static class MethodBasedDecorator<T extends MetaDataObject, A extends Annotation> extends AnnotationBasedMetaDataDecorator<T, A>
188 {
189 private final Object target;
190 private final Method method;
191
192 private MethodBasedDecorator(Class<A> annotationType, Object target, Method method)
193 {
194 super(annotationType);
195 this.target = target;
196 this.method = method;
197 }
198
199 @Override
200 protected void decorate(T metaData, Annotation annotation)
201 {
202 try
203 {
204 method.invoke(target, metaData, annotation);
205 }
206 catch (IllegalAccessException e)
207 {
208 throw new RuntimeException("Unable to access decorator method " + method + ".", e);
209 }
210 catch (InvocationTargetException e)
211 {
212 throw new RuntimeException("Decorator method " + method + " threw an exception.", e.getTargetException());
213 }
214 }
215 }
216 }