Make small fixes
[smart-dao.git] / smart-exim / smart-exim-api / src / main / java / com / smartitengineering / exim / impl / DefaultAnnotationConfigScanner.java
blob61ce2ce04d309c9dca040adde7f51d5fbe91d9cf
1 /*
2 * This is a common dao with basic CRUD operations and is not limited to any
3 * persistent layer implementation
4 *
5 * Copyright (C) 2008 Imran M Yousuf (imyousuf@smartitengineering.com)
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 3 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 package com.smartitengineering.exim.impl;
21 import com.smartitengineering.domain.annotations.Eager;
22 import com.smartitengineering.domain.annotations.Export;
23 import com.smartitengineering.domain.annotations.Id;
24 import com.smartitengineering.domain.annotations.Name;
25 import com.smartitengineering.domain.annotations.ResourceDomain;
26 import com.smartitengineering.domain.exim.DomainSelfExporter;
27 import com.smartitengineering.domain.exim.DomainSelfImporter;
28 import com.smartitengineering.domain.exim.IdentityCustomizer;
29 import com.smartitengineering.domain.exim.StringValueProvider;
30 import com.smartitengineering.exim.AssociationConfig;
31 import com.smartitengineering.exim.ClassConfigScanner;
32 import com.smartitengineering.exim.ConfigRegistrar;
33 import com.smartitengineering.exim.EximResourceConfig;
34 import com.smartitengineering.exim.PackageConfigScanner;
35 import com.smartitengineering.util.simple.IOFactory;
36 import com.smartitengineering.util.simple.reflection.AnnotationConfig;
37 import com.smartitengineering.util.simple.reflection.ClassAnnotationVisitorImpl;
38 import com.smartitengineering.util.simple.reflection.ClassScanner;
39 import com.smartitengineering.util.simple.reflection.VisitCallback;
40 import java.lang.annotation.Annotation;
41 import java.lang.reflect.AnnotatedElement;
42 import java.lang.reflect.Field;
43 import java.lang.reflect.Method;
44 import java.util.Collection;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.Map;
48 import java.util.Set;
50 /**
51 * This registrar is responsible for scanning and containing configuration of
52 * all resources. Whenever a class is looked up whose package has not been
53 * scanned yet, registrar will scanClassForConfig its package to generate cofiguration and
54 * while generating it will keep scanning until it reaches the leaves, i.e. in
55 * this case association to an object that isn't {@link ResourceDomain}
56 * @author imyousuf
57 * @since 0.4
59 public class DefaultAnnotationConfigScanner
60 implements ClassConfigScanner,
61 PackageConfigScanner {
63 private static final String GETTER_PREFIX = "get";
64 private static DefaultAnnotationConfigScanner registrar;
66 static {
67 ConfigRegistrar.registerClassScanner(
68 DefaultAnnotationConfigScanner.class, 25);
69 ConfigRegistrar.registerPackageScanner(
70 DefaultAnnotationConfigScanner.class, 25);
73 public static DefaultAnnotationConfigScanner getInstance() {
74 if (registrar == null) {
75 registrar = new DefaultAnnotationConfigScanner();
77 return registrar;
79 protected final ClassScanner classScanner;
80 protected final Map<Class, EximResourceConfig> configuraitons;
81 protected final Collection<Class> scannedClasses;
82 protected final Collection<String> scannedPackages;
83 protected final ResourceVisitCallback resourceVisitCallback;
84 protected final VisitCallback<AnnotationConfig> callbackHandler;
86 protected DefaultAnnotationConfigScanner() {
87 classScanner = IOFactory.getDefaultClassScanner();
88 configuraitons = new HashMap<Class, EximResourceConfig>();
89 scannedClasses = new HashSet<Class>();
90 scannedPackages = new HashSet<String>();
91 resourceVisitCallback = new ResourceVisitCallback();
92 callbackHandler = resourceVisitCallback;
95 public synchronized Map<Class, EximResourceConfig> getConfigurations() {
96 return configuraitons;
99 public synchronized Collection<Class> getConfiguredResourceClasses() {
100 return configuraitons.keySet();
104 * Retrieves the configuration for the resource class, if its not already
105 * generated then it will search the package of the class and generate its
106 * configuraiton.
107 * @param resourceClass The class to scanClassForConfig retrieve configuration for
108 * @return Configuration for the resource; NULL if no resource is available
109 * or generateable
110 * @throws IllegalArgumentException If resource class is null!
112 public synchronized EximResourceConfig getResourceConfigForClass(
113 final Class resourceClass) {
114 if (resourceClass == null) {
115 throw new IllegalArgumentException("Resource class can't be null!");
117 if (configuraitons.containsKey(resourceClass)) {
118 return configuraitons.get(resourceClass);
120 else {
121 String packageName = resourceClass.getPackage().getName();
122 EximResourceConfig resourceConfig = null;
123 if (!scannedPackages.contains(packageName)) {
124 resourceConfig = scanPackage(Package.getPackage(packageName),
125 resourceClass);
127 return resourceConfig;
132 * Scans and prepares all configurations in the package and makes it
133 * available for future use.
134 * @param resourcePackage Package to scan and gather configuration
135 * @throws IllegalArgumentException If package is null
137 public synchronized void scanPackageForResourceConfigs(
138 final Package resourcePackage) {
139 if (resourcePackage == null) {
140 throw new IllegalArgumentException("Resource class can't be null!");
142 scanPackage(resourcePackage, null);
145 protected String getPropertyNameFromMethodName(final String methodName) {
146 StringBuilder propertyNameBuilder =
147 new StringBuilder(methodName);
148 propertyNameBuilder.delete(0, GETTER_PREFIX.length());
149 char firstChar = propertyNameBuilder.charAt(0);
150 propertyNameBuilder.delete(0, 1);
151 propertyNameBuilder.insert(0, Character.toLowerCase(firstChar));
152 return propertyNameBuilder.toString();
156 * Scan among the class's annotations to find the required configuration for
157 * exporting and importing resources.
158 * @param probableResourceClass The probable resource domain class.
159 * @return The configuration of the class, null if not annotated with
160 * {@link ResourceDomain}
162 protected EximResourceConfig scanClassForConfig(
163 final Class probableResourceClass) {
164 if(probableResourceClass == null) {
165 return null;
167 if (scannedClasses.contains(probableResourceClass)) {
168 return getConfigurations().get(probableResourceClass);
170 Annotation annotation = probableResourceClass.getAnnotation(
171 ResourceDomain.class);
172 if (annotation == null) {
173 return null;
175 EximResourceConfigImpl resourceConfig = new EximResourceConfigImpl();
176 resourceConfig.setDomainClass(probableResourceClass);
177 Name nameAnnotation = (Name) probableResourceClass.getAnnotation(
178 Name.class);
179 if (nameAnnotation != null) {
180 resourceConfig.setName(nameAnnotation.value());
182 else {
183 resourceConfig.setName(probableResourceClass.getName());
185 ResourceDomain domainAnnotation = (ResourceDomain) annotation;
186 resourceConfig.setAccessByPropertyEnabled(domainAnnotation.
187 accessByProperty());
188 resourceConfig.setAssociateExportPolicyAsUri(domainAnnotation.
189 exportAsURIByDefault());
190 resourceConfig.setPathToResource(domainAnnotation.path());
191 resourceConfig.setExporterImplemented(DomainSelfExporter.class.
192 isAssignableFrom(probableResourceClass));
193 resourceConfig.setImporterImplemented(DomainSelfImporter.class.
194 isAssignableFrom(probableResourceClass));
195 resourceConfig.setIdentityCustomizerImplemented(
196 IdentityCustomizer.class.isAssignableFrom(probableResourceClass));
197 scanMembers(resourceConfig, probableResourceClass);
198 scannedClasses.add(probableResourceClass);
199 configuraitons.put(probableResourceClass, resourceConfig);
200 return resourceConfig;
204 * Scan a package for extracting configurations of resource domains.
205 * @param packageName Package to scanClassForConfig.
206 * @param resourceClass Main class scanClassForConfig requested for
207 * @return The configuration of the resource class, Null if the class is not
208 * a domain class or resourceClass is null
209 * @throws java.lang.IllegalArgumentException If package is null
211 protected EximResourceConfig scanPackage(final Package packageToScan,
212 final Class resourceClass)
213 throws IllegalArgumentException {
214 if (packageToScan == null) {
215 throw new IllegalArgumentException();
217 EximResourceConfig resourceConfig = null;
218 classScanner.scan(new String[]{packageToScan.getName()},
219 new ClassAnnotationVisitorImpl(callbackHandler,
220 IOFactory.getAnnotationNameForVisitor(ResourceDomain.class)));
221 Set<String> classPaths = resourceVisitCallback.getProbableResources();
222 if (!classPaths.isEmpty()) {
223 for (String classPath : classPaths) {
224 try {
225 Class probableResourceClass =
226 IOFactory.getClassFromVisitorName(classPath);
227 EximResourceConfig config = scanClassForConfig(
228 probableResourceClass);
229 if (config != null && resourceClass != null &&
230 probableResourceClass.equals(resourceClass)) {
231 resourceConfig = config;
234 catch (Exception ex) {
238 scannedPackages.add(packageToScan.getName());
239 return resourceConfig;
243 * It will scan all member attributes and behavior based on configuration on
244 * the class. It will also scan all inherited attributes and behavior.
245 * @param resourceConfig The config representing the domain class
246 * @param resourceClass The domain class
248 protected void scanMembers(final EximResourceConfigImpl resourceConfig,
249 final Class resourceClass) {
250 if (resourceConfig.isAccessByPropertyEnabled()) {
251 scanMethods(resourceConfig, resourceClass);
253 else {
254 scanFields(resourceConfig, resourceClass);
259 * Scans getter methods for discovering associations of the domain and thier
260 * respective configurations. It will only scan public getter methods.
261 * @param resourceConfig The config of the domain resource
262 * @param resourceClass The domain class
264 protected void scanMethods(final EximResourceConfigImpl resourceConfig,
265 final Class resourceClass) {
266 Method[] methods = resourceClass.getMethods();
267 if (methods == null || methods.length <= 0) {
268 return;
270 for (Method method : methods) {
271 String methodName = method.getName();
272 //Only scan getter methods as of bean spec, that getter methods with
273 //no paratmeters and non-void return types
274 if (methodName.startsWith(GETTER_PREFIX) && methodName.length() >
275 GETTER_PREFIX.length() && method.getReturnType() != null &&
276 !method.getReturnType().equals(Void.class) &&
277 (method.getParameterTypes() == null ||
278 method.getParameterTypes().length <= 0)) {
279 scanGetterMethod(resourceConfig, method);
285 * Scans a getter method for annotations which is used to cofigure the
286 * nature of the export
287 * @param resourceConfig The config to populate with configurations
288 * @param method The getter method to scan
289 * @throws java.lang.IllegalArgumentException If its not a getter method
290 * with non-void return type and
291 * with a non-zero length bean
292 * name length and with no
293 * parameter
295 protected void scanGetterMethod(final EximResourceConfigImpl resourceConfig,
296 final Method method)
297 throws IllegalArgumentException {
298 String methodName = method.getName();
299 if (!(methodName.startsWith(GETTER_PREFIX) && methodName.length() >
300 GETTER_PREFIX.length() && method.getReturnType() != null &&
301 !method.getReturnType().equals(Void.class) &&
302 (method.getParameterTypes() == null ||
303 method.getParameterTypes().length <= 0))) {
304 throw new IllegalArgumentException();
306 String propertyName = getPropertyNameFromMethodName(methodName);
307 Class returnType = method.getReturnType();
308 scanAnnotatedElement(method, propertyName, returnType, resourceConfig);
312 * Scans fields for discovering associations of the domain and thier
313 * respective configurations
314 * @param resourceConfig The config of the domain resource
315 * @param resourceClass The domain class
317 protected void scanFields(EximResourceConfigImpl resourceConfig,
318 Class resourceClass) {
319 Field[] fields = resourceClass.getDeclaredFields();
320 for (Field field : fields) {
321 scanField(resourceConfig, field);
323 Class parentClass = resourceClass.getSuperclass();
324 if (!parentClass.equals(Object.class)) {
325 scanFields(resourceConfig, parentClass);
330 * A field is scanned for gathering configurations for export-import.
331 * @param resourceConfig Configuraton for the field association
332 * @param field The field to scan
334 protected void scanField(final EximResourceConfigImpl resourceConfig,
335 final Field field) {
336 String propertyName = field.getName();
337 Class propertyType = field.getType();
338 scanAnnotatedElement(field, propertyName, propertyType, resourceConfig);
342 * Scan an ennotated element to extract configuration information
343 * @param element Element to scan
344 * @param propertyName The name of the property scanning
345 * @param propertyType The type of the property
346 * @param resourceConfig The configuration for the domain class
347 * @throws java.lang.IllegalArgumentException If any argument is null
349 protected void scanAnnotatedElement(final AnnotatedElement element,
350 final String propertyName,
351 final Class propertyType,
352 final EximResourceConfigImpl resourceConfig)
353 throws IllegalArgumentException {
354 if (element == null || propertyName == null || propertyType == null ||
355 resourceConfig == null) {
356 throw new IllegalArgumentException();
358 Export annotation =
359 element.getAnnotation(Export.class);
360 AssociationConfigImpl configImpl =
361 new AssociationConfigImpl();
362 configImpl.setName(propertyName);
363 configImpl.setAssociationType(AssociationConfig.AssociationType.
364 getAssociationType(propertyType));
365 Eager eager =
366 element.getAnnotation(Eager.class);
367 configImpl.setStringProviderImplemented(StringValueProvider.class.
368 isAssignableFrom(propertyType));
369 configImpl.setEagerSet(eager != null);
370 if (annotation != null) {
371 configImpl.setItToBeExportedAsUri(!annotation.asObject());
372 configImpl.setTransient(annotation.isTransient());
374 else {
375 configImpl.setItToBeExportedAsUri(false);
376 configImpl.setTransient(false);
378 resourceConfig.getAssociationConfigs().put(propertyName, configImpl);
379 Id id =
380 element.getAnnotation(Id.class);
381 if (id != null) {
382 resourceConfig.setIdPropertyName(propertyName);
387 * The visitor callback to be notified for dmain classes.
389 protected static class ResourceVisitCallback
390 implements VisitCallback<AnnotationConfig> {
392 private Set<String> probableResources = new HashSet<String>();
394 public void handle(AnnotationConfig config) {
395 probableResources.add(config.getClassName());
398 public Set<String> getProbableResources() {
399 return probableResources;