Add documentation to some missing classes
[smart-dao.git] / smart-exim / smart-exim-api / src / main / java / com / smartitengineering / exim / impl / DefaultAnnotationConfigScanner.java
blob8647ecd65f6493da75aa17bfffd1688e1a23a5c8
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 final String IS_PREFIX = "is";
65 private static final String HAS_PREFIX = "has";
66 private static DefaultAnnotationConfigScanner registrar;
68 static {
69 ConfigRegistrar.registerClassScanner(
70 DefaultAnnotationConfigScanner.class, 25);
71 ConfigRegistrar.registerPackageScanner(
72 DefaultAnnotationConfigScanner.class, 25);
75 /**
76 * Get a singleton instance of the default annotation config scanner; it
77 * will initialize the singleton instance lazily, i.e. upon first request.
78 * @return The singleton package & class annotation config scanner
80 public static DefaultAnnotationConfigScanner getInstance() {
81 if (registrar == null) {
82 registrar = new DefaultAnnotationConfigScanner();
84 return registrar;
86 /**
87 * The class scanner scanning for specified annotations
89 protected final ClassScanner classScanner;
90 /**
91 * The configuration map for holding all resource configurations against
92 * their domain class.
94 protected final Map<Class, EximResourceConfig> configuraitons;
95 /**
96 * Collection of all classes scanned till thus
98 protected final Collection<Class> scannedClasses;
99 /**
100 * Collection of all packages scanned till thus
102 protected final Collection<String> scannedPackages;
104 * This is the annotation visit callback handling resources only
106 protected final ResourceVisitCallback resourceVisitCallback;
108 * The callback handler to be invoked by the class scanner
110 protected final VisitCallback<AnnotationConfig> callbackHandler;
113 * Initializes the member variables only.
115 protected DefaultAnnotationConfigScanner() {
116 classScanner = IOFactory.getDefaultClassScanner();
117 configuraitons = new HashMap<Class, EximResourceConfig>();
118 scannedClasses = new HashSet<Class>();
119 scannedPackages = new HashSet<String>();
120 resourceVisitCallback = new ResourceVisitCallback();
121 callbackHandler = resourceVisitCallback;
124 public synchronized Map<Class, EximResourceConfig> getConfigurations() {
125 return configuraitons;
128 public synchronized Collection<Class> getConfiguredResourceClasses() {
129 return configuraitons.keySet();
133 * Retrieves the configuration for the resource class, if its not already
134 * generated then it will search the package of the class and generate its
135 * configuraiton.
136 * @param resourceClass The class to scanClassForConfig retrieve configuration for
137 * @return Configuration for the resource; NULL if no resource is available
138 * or generateable
139 * @throws IllegalArgumentException If resource class is null!
141 public synchronized EximResourceConfig getResourceConfigForClass(
142 final Class resourceClass) {
143 if (resourceClass == null) {
144 throw new IllegalArgumentException("Resource class can't be null!");
146 if (configuraitons.containsKey(resourceClass)) {
147 return configuraitons.get(resourceClass);
149 else {
150 String packageName = resourceClass.getPackage().getName();
151 EximResourceConfig resourceConfig = null;
152 if (!scannedPackages.contains(packageName)) {
153 resourceConfig = scanPackage(Package.getPackage(packageName),
154 resourceClass);
156 return resourceConfig;
161 * Scans and prepares all configurations in the package and makes it
162 * available for future use.
163 * @param resourcePackage Package to scan and gather configuration
164 * @throws IllegalArgumentException If package is null
166 public synchronized void scanPackageForResourceConfigs(
167 final Package resourcePackage) {
168 if (resourcePackage == null) {
169 throw new IllegalArgumentException("Resource class can't be null!");
171 scanPackage(resourcePackage, null);
175 * Given a method name it returns the property name for it. Currently it
176 * supports methods starting with "get", "is" and "has".
177 * @param methodName The method name from which to derive the property name
178 * @return Property name represented by the read accessor
179 * @throws IllegalArgumentException If prefix is not supported
180 * @throws NullPointerException If method name is null
182 protected String getPropertyNameFromMethodName(final String methodName)
183 throws IllegalArgumentException,
184 NullPointerException {
185 StringBuilder propertyNameBuilder =
186 new StringBuilder(methodName);
187 int cutLength = -1;
188 if (methodName.startsWith(GETTER_PREFIX)) {
189 cutLength = GETTER_PREFIX.length();
191 else if (methodName.startsWith(IS_PREFIX)) {
192 cutLength = IS_PREFIX.length();
194 else if (methodName.startsWith(HAS_PREFIX)) {
195 cutLength = HAS_PREFIX.length();
197 else {
198 throw new IllegalArgumentException(
199 "Not a valid property read accoessor");
201 propertyNameBuilder.delete(0, cutLength);
202 char firstChar = propertyNameBuilder.charAt(0);
203 propertyNameBuilder.delete(0, 1);
204 propertyNameBuilder.insert(0, Character.toLowerCase(firstChar));
205 return propertyNameBuilder.toString();
209 * Scan among the class's annotations to find the required configuration for
210 * exporting and importing resources.
211 * @param probableResourceClass The probable resource domain class.
212 * @return The configuration of the class, null if not annotated with
213 * {@link ResourceDomain}
215 protected EximResourceConfig scanClassForConfig(
216 final Class probableResourceClass) {
217 if (probableResourceClass == null) {
218 return null;
220 if (scannedClasses.contains(probableResourceClass)) {
221 return getConfigurations().get(probableResourceClass);
223 Annotation annotation = probableResourceClass.getAnnotation(
224 ResourceDomain.class);
225 if (annotation == null) {
226 return null;
228 EximResourceConfigImpl resourceConfig = new EximResourceConfigImpl();
229 resourceConfig.setDomainClass(probableResourceClass);
230 Name nameAnnotation = (Name) probableResourceClass.getAnnotation(
231 Name.class);
232 if (nameAnnotation != null) {
233 resourceConfig.setName(nameAnnotation.value());
235 else {
236 resourceConfig.setName(probableResourceClass.getName());
238 ResourceDomain domainAnnotation = (ResourceDomain) annotation;
239 resourceConfig.setAccessByPropertyEnabled(domainAnnotation.
240 accessByProperty());
241 resourceConfig.setAssociateExportPolicyAsUri(domainAnnotation.
242 exportAsURIByDefault());
243 resourceConfig.setPathToResource(domainAnnotation.path());
244 resourceConfig.setExporterImplemented(DomainSelfExporter.class.
245 isAssignableFrom(probableResourceClass));
246 resourceConfig.setImporterImplemented(DomainSelfImporter.class.
247 isAssignableFrom(probableResourceClass));
248 resourceConfig.setIdentityCustomizerImplemented(
249 IdentityCustomizer.class.isAssignableFrom(probableResourceClass));
250 resourceConfig.setPriority(domainAnnotation.priority());
251 scanMembers(resourceConfig, probableResourceClass);
252 scannedClasses.add(probableResourceClass);
253 //If domain id is not specified then its not a valid domain
254 if (!resourceConfig.isIdentityCustomizerImplemented() &&
255 (resourceConfig.getIdPropertyName() == null || resourceConfig.
256 getIdPropertyName().equals(""))) {
257 return null;
259 else {
260 configuraitons.put(probableResourceClass, resourceConfig);
261 return resourceConfig;
266 * Scan a package for extracting configurations of resource domains.
267 * @param packageToScan Package to scanClassForConfig.
268 * @param resourceClass Main class scanClassForConfig requested for
269 * @return The configuration of the resource class, Null if the class is not
270 * a domain class or resourceClass is null
271 * @throws java.lang.IllegalArgumentException If package is null
273 protected EximResourceConfig scanPackage(final Package packageToScan,
274 final Class resourceClass)
275 throws IllegalArgumentException {
276 if (packageToScan == null) {
277 throw new IllegalArgumentException();
279 EximResourceConfig resourceConfig = null;
280 classScanner.scan(new String[]{packageToScan.getName()},
281 new ClassAnnotationVisitorImpl(callbackHandler,
282 IOFactory.getAnnotationNameForVisitor(ResourceDomain.class)));
283 Set<String> classPaths = resourceVisitCallback.getProbableResources();
284 if (!classPaths.isEmpty()) {
285 for (String classPath : classPaths) {
286 try {
287 Class probableResourceClass =
288 IOFactory.getClassFromVisitorName(classPath);
289 EximResourceConfig config = scanClassForConfig(
290 probableResourceClass);
291 if (config != null && resourceClass != null &&
292 probableResourceClass.equals(resourceClass)) {
293 resourceConfig = config;
296 catch (Exception ex) {
300 scannedPackages.add(packageToScan.getName());
301 return resourceConfig;
305 * It will scan all member attributes and behavior based on configuration on
306 * the class. It will also scan all inherited attributes and behavior.
307 * @param resourceConfig The config representing the domain class
308 * @param resourceClass The domain class
310 protected void scanMembers(final EximResourceConfigImpl resourceConfig,
311 final Class resourceClass) {
312 if (resourceConfig.isAccessByPropertyEnabled()) {
313 scanMethods(resourceConfig, resourceClass);
315 else {
316 scanFields(resourceConfig, resourceClass);
321 * Scans getter methods for discovering associations of the domain and thier
322 * respective configurations. It will only scan public getter methods.
323 * @param resourceConfig The config of the domain resource
324 * @param resourceClass The domain class
326 protected void scanMethods(final EximResourceConfigImpl resourceConfig,
327 final Class resourceClass) {
328 Method[] methods = resourceClass.getMethods();
329 if (methods == null || methods.length <= 0) {
330 return;
332 for (Method method : methods) {
333 String methodName = method.getName();
334 //Only scan getter methods as of bean spec, that getter methods with
335 //no paratmeters and non-void return types
336 if (((methodName.startsWith(GETTER_PREFIX) && methodName.length() >
337 GETTER_PREFIX.length()) || (methodName.startsWith(IS_PREFIX) &&
338 methodName.length() > IS_PREFIX.length()) || (methodName.
339 startsWith(HAS_PREFIX) && methodName.length() > HAS_PREFIX.
340 length())) && method.getReturnType() != null && !method.
341 getReturnType().equals(Void.class) &&
342 (method.getParameterTypes() == null ||
343 method.getParameterTypes().length <= 0)) {
344 scanGetterMethod(resourceConfig, method);
350 * Scans a getter method for annotations which is used to cofigure the
351 * nature of the export
352 * @param resourceConfig The config to populate with configurations
353 * @param method The getter method to scan
354 * @throws java.lang.IllegalArgumentException If its not a getter method
355 * with non-void return type and
356 * with a non-zero length bean
357 * name length and with no
358 * parameter
360 protected void scanGetterMethod(final EximResourceConfigImpl resourceConfig,
361 final Method method)
362 throws IllegalArgumentException {
363 String methodName = method.getName();
364 //Ignore the getClass bean
365 if (method.getName().equals("getClass")) {
366 return;
368 if (!(((methodName.startsWith(GETTER_PREFIX) && methodName.length() >
369 GETTER_PREFIX.length()) || (methodName.startsWith(IS_PREFIX) &&
370 methodName.length() > IS_PREFIX.length()) || (methodName.startsWith(
371 HAS_PREFIX) && methodName.length() > HAS_PREFIX.length())) &&
372 method.getReturnType() != null && !method.getReturnType().equals(
373 Void.class) && (method.getParameterTypes() == null || method.
374 getParameterTypes().length <= 0))) {
375 throw new IllegalArgumentException();
377 String propertyName = getPropertyNameFromMethodName(methodName);
378 Class returnType = method.getReturnType();
379 scanAnnotatedElement(method, propertyName, returnType, resourceConfig);
383 * Scans fields for discovering associations of the domain and thier
384 * respective configurations
385 * @param resourceConfig The config of the domain resource
386 * @param resourceClass The domain class
388 protected void scanFields(EximResourceConfigImpl resourceConfig,
389 Class resourceClass) {
390 Field[] fields = resourceClass.getDeclaredFields();
391 for (Field field : fields) {
392 scanField(resourceConfig, field);
394 Class parentClass = resourceClass.getSuperclass();
395 if (!parentClass.equals(Object.class)) {
396 scanFields(resourceConfig, parentClass);
401 * A field is scanned for gathering configurations for export-import.
402 * @param resourceConfig Configuraton for the field association
403 * @param field The field to scan
405 protected void scanField(final EximResourceConfigImpl resourceConfig,
406 final Field field) {
407 String propertyName = field.getName();
408 Class propertyType = field.getType();
409 scanAnnotatedElement(field, propertyName, propertyType, resourceConfig);
413 * Scan an ennotated element to extract configuration information
414 * @param element Element to scan
415 * @param propertyName The name of the property scanning
416 * @param propertyType The type of the property
417 * @param resourceConfig The configuration for the domain class
418 * @throws java.lang.IllegalArgumentException If any argument is null
420 protected void scanAnnotatedElement(final AnnotatedElement element,
421 final String propertyName,
422 final Class propertyType,
423 final EximResourceConfigImpl resourceConfig)
424 throws IllegalArgumentException {
425 if (element == null || propertyName == null || propertyType == null ||
426 resourceConfig == null) {
427 throw new IllegalArgumentException();
429 Export annotation =
430 element.getAnnotation(Export.class);
431 AssociationConfigImpl configImpl =
432 new AssociationConfigImpl();
433 configImpl.setName(propertyName);
434 configImpl.setAssociationType(AssociationConfig.AssociationType.
435 getAssociationType(propertyType));
436 Eager eager =
437 element.getAnnotation(Eager.class);
438 configImpl.setStringProviderImplemented(StringValueProvider.class.
439 isAssignableFrom(propertyType));
440 configImpl.setEagerSet(eager != null);
441 if (annotation != null) {
442 configImpl.setItToBeExportedAsUri(!annotation.asObject());
443 configImpl.setTransient(annotation.isTransient());
445 else {
446 configImpl.setItToBeExportedAsUri(false);
447 configImpl.setTransient(false);
449 resourceConfig.getAssociationConfigs().put(propertyName, configImpl);
450 Id id =
451 element.getAnnotation(Id.class);
452 if (id != null) {
453 resourceConfig.setIdPropertyName(propertyName);
454 resourceConfig.setIdPrefix(id.path());
459 * The visitor callback to be notified for dmain classes.
461 protected static class ResourceVisitCallback
462 implements VisitCallback<AnnotationConfig> {
464 private Set<String> probableResources = new HashSet<String>();
467 * When a requested annotation is been parsed the callback will be
468 * triggered and it will maintain a {@link Set} of scanned classes and
469 * mark them as probable resource
470 * @param config The annotation been parsed
472 public void handle(AnnotationConfig config) {
473 probableResources.add(config.getClassName());
477 * Return the probable resources scanned upto now
478 * @return Probable resources
480 public Set<String> getProbableResources() {
481 return probableResources;