Add priority to config API
[smart-dao.git] / smart-exim / smart-exim-api / src / main / java / com / smartitengineering / exim / impl / DefaultAnnotationConfigScanner.java
blob40c0bbcd1a6cfc39fdce2e304b66abdb0e9e7f4f
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 resourceConfig.setPriority(domainAnnotation.priority());
198 scanMembers(resourceConfig, probableResourceClass);
199 scannedClasses.add(probableResourceClass);
200 configuraitons.put(probableResourceClass, resourceConfig);
201 return resourceConfig;
205 * Scan a package for extracting configurations of resource domains.
206 * @param packageName Package to scanClassForConfig.
207 * @param resourceClass Main class scanClassForConfig requested for
208 * @return The configuration of the resource class, Null if the class is not
209 * a domain class or resourceClass is null
210 * @throws java.lang.IllegalArgumentException If package is null
212 protected EximResourceConfig scanPackage(final Package packageToScan,
213 final Class resourceClass)
214 throws IllegalArgumentException {
215 if (packageToScan == null) {
216 throw new IllegalArgumentException();
218 EximResourceConfig resourceConfig = null;
219 classScanner.scan(new String[]{packageToScan.getName()},
220 new ClassAnnotationVisitorImpl(callbackHandler,
221 IOFactory.getAnnotationNameForVisitor(ResourceDomain.class)));
222 Set<String> classPaths = resourceVisitCallback.getProbableResources();
223 if (!classPaths.isEmpty()) {
224 for (String classPath : classPaths) {
225 try {
226 Class probableResourceClass =
227 IOFactory.getClassFromVisitorName(classPath);
228 EximResourceConfig config = scanClassForConfig(
229 probableResourceClass);
230 if (config != null && resourceClass != null &&
231 probableResourceClass.equals(resourceClass)) {
232 resourceConfig = config;
235 catch (Exception ex) {
239 scannedPackages.add(packageToScan.getName());
240 return resourceConfig;
244 * It will scan all member attributes and behavior based on configuration on
245 * the class. It will also scan all inherited attributes and behavior.
246 * @param resourceConfig The config representing the domain class
247 * @param resourceClass The domain class
249 protected void scanMembers(final EximResourceConfigImpl resourceConfig,
250 final Class resourceClass) {
251 if (resourceConfig.isAccessByPropertyEnabled()) {
252 scanMethods(resourceConfig, resourceClass);
254 else {
255 scanFields(resourceConfig, resourceClass);
260 * Scans getter methods for discovering associations of the domain and thier
261 * respective configurations. It will only scan public getter methods.
262 * @param resourceConfig The config of the domain resource
263 * @param resourceClass The domain class
265 protected void scanMethods(final EximResourceConfigImpl resourceConfig,
266 final Class resourceClass) {
267 Method[] methods = resourceClass.getMethods();
268 if (methods == null || methods.length <= 0) {
269 return;
271 for (Method method : methods) {
272 String methodName = method.getName();
273 //Only scan getter methods as of bean spec, that getter methods with
274 //no paratmeters and non-void return types
275 if (methodName.startsWith(GETTER_PREFIX) && methodName.length() >
276 GETTER_PREFIX.length() && method.getReturnType() != null &&
277 !method.getReturnType().equals(Void.class) &&
278 (method.getParameterTypes() == null ||
279 method.getParameterTypes().length <= 0)) {
280 scanGetterMethod(resourceConfig, method);
286 * Scans a getter method for annotations which is used to cofigure the
287 * nature of the export
288 * @param resourceConfig The config to populate with configurations
289 * @param method The getter method to scan
290 * @throws java.lang.IllegalArgumentException If its not a getter method
291 * with non-void return type and
292 * with a non-zero length bean
293 * name length and with no
294 * parameter
296 protected void scanGetterMethod(final EximResourceConfigImpl resourceConfig,
297 final Method method)
298 throws IllegalArgumentException {
299 String methodName = method.getName();
300 //Ignore the getClass bean
301 if(method.getName().equals("getClass")) {
302 return ;
304 if (!(methodName.startsWith(GETTER_PREFIX) && methodName.length() >
305 GETTER_PREFIX.length() && method.getReturnType() != null &&
306 !method.getReturnType().equals(Void.class) &&
307 (method.getParameterTypes() == null ||
308 method.getParameterTypes().length <= 0))) {
309 throw new IllegalArgumentException();
311 String propertyName = getPropertyNameFromMethodName(methodName);
312 Class returnType = method.getReturnType();
313 scanAnnotatedElement(method, propertyName, returnType, resourceConfig);
317 * Scans fields for discovering associations of the domain and thier
318 * respective configurations
319 * @param resourceConfig The config of the domain resource
320 * @param resourceClass The domain class
322 protected void scanFields(EximResourceConfigImpl resourceConfig,
323 Class resourceClass) {
324 Field[] fields = resourceClass.getDeclaredFields();
325 for (Field field : fields) {
326 scanField(resourceConfig, field);
328 Class parentClass = resourceClass.getSuperclass();
329 if (!parentClass.equals(Object.class)) {
330 scanFields(resourceConfig, parentClass);
335 * A field is scanned for gathering configurations for export-import.
336 * @param resourceConfig Configuraton for the field association
337 * @param field The field to scan
339 protected void scanField(final EximResourceConfigImpl resourceConfig,
340 final Field field) {
341 String propertyName = field.getName();
342 Class propertyType = field.getType();
343 scanAnnotatedElement(field, propertyName, propertyType, resourceConfig);
347 * Scan an ennotated element to extract configuration information
348 * @param element Element to scan
349 * @param propertyName The name of the property scanning
350 * @param propertyType The type of the property
351 * @param resourceConfig The configuration for the domain class
352 * @throws java.lang.IllegalArgumentException If any argument is null
354 protected void scanAnnotatedElement(final AnnotatedElement element,
355 final String propertyName,
356 final Class propertyType,
357 final EximResourceConfigImpl resourceConfig)
358 throws IllegalArgumentException {
359 if (element == null || propertyName == null || propertyType == null ||
360 resourceConfig == null) {
361 throw new IllegalArgumentException();
363 Export annotation =
364 element.getAnnotation(Export.class);
365 AssociationConfigImpl configImpl =
366 new AssociationConfigImpl();
367 configImpl.setName(propertyName);
368 configImpl.setAssociationType(AssociationConfig.AssociationType.
369 getAssociationType(propertyType));
370 Eager eager =
371 element.getAnnotation(Eager.class);
372 configImpl.setStringProviderImplemented(StringValueProvider.class.
373 isAssignableFrom(propertyType));
374 configImpl.setEagerSet(eager != null);
375 if (annotation != null) {
376 configImpl.setItToBeExportedAsUri(!annotation.asObject());
377 configImpl.setTransient(annotation.isTransient());
379 else {
380 configImpl.setItToBeExportedAsUri(false);
381 configImpl.setTransient(false);
383 resourceConfig.getAssociationConfigs().put(propertyName, configImpl);
384 Id id =
385 element.getAnnotation(Id.class);
386 if (id != null) {
387 resourceConfig.setIdPropertyName(propertyName);
392 * The visitor callback to be notified for dmain classes.
394 protected static class ResourceVisitCallback
395 implements VisitCallback<AnnotationConfig> {
397 private Set<String> probableResources = new HashSet<String>();
399 public void handle(AnnotationConfig config) {
400 probableResources.add(config.getClassName());
403 public Set<String> getProbableResources() {
404 return probableResources;