2 * This is a common dao with basic CRUD operations and is not limited to any
3 * persistent layer implementation
5 * Copyright (C) 2008 Imran M Yousuf (imyousuf@smartitengineering.com)
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
;
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}
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
;
69 ConfigRegistrar
.registerClassScanner(
70 DefaultAnnotationConfigScanner
.class, 25);
71 ConfigRegistrar
.registerPackageScanner(
72 DefaultAnnotationConfigScanner
.class, 25);
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();
87 * The class scanner scanning for specified annotations
89 protected final ClassScanner classScanner
;
91 * The configuration map for holding all resource configurations against
94 protected final Map
<Class
, EximResourceConfig
> configuraitons
;
96 * Collection of all classes scanned till thus
98 protected final Collection
<Class
> scannedClasses
;
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
136 * @param resourceClass The class to scanClassForConfig retrieve configuration for
137 * @return Configuration for the resource; NULL if no resource is available
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
);
150 String packageName
= resourceClass
.getPackage().getName();
151 EximResourceConfig resourceConfig
= null;
152 if (!scannedPackages
.contains(packageName
)) {
153 resourceConfig
= scanPackage(Package
.getPackage(packageName
),
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
);
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();
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) {
220 if (scannedClasses
.contains(probableResourceClass
)) {
221 return getConfigurations().get(probableResourceClass
);
223 Annotation annotation
= probableResourceClass
.getAnnotation(
224 ResourceDomain
.class);
225 if (annotation
== null) {
228 EximResourceConfigImpl resourceConfig
= new EximResourceConfigImpl();
229 resourceConfig
.setDomainClass(probableResourceClass
);
230 Name nameAnnotation
= (Name
) probableResourceClass
.getAnnotation(
232 if (nameAnnotation
!= null) {
233 resourceConfig
.setName(nameAnnotation
.value());
236 resourceConfig
.setName(probableResourceClass
.getName());
238 ResourceDomain domainAnnotation
= (ResourceDomain
) annotation
;
239 resourceConfig
.setAccessByPropertyEnabled(domainAnnotation
.
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(""))) {
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
) {
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
);
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) {
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
360 protected void scanGetterMethod(final EximResourceConfigImpl resourceConfig
,
362 throws IllegalArgumentException
{
363 String methodName
= method
.getName();
364 //Ignore the getClass bean
365 if (method
.getName().equals("getClass")) {
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
,
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();
430 element
.getAnnotation(Export
.class);
431 AssociationConfigImpl configImpl
=
432 new AssociationConfigImpl();
433 configImpl
.setName(propertyName
);
434 configImpl
.setAssociationType(AssociationConfig
.AssociationType
.
435 getAssociationType(propertyType
));
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());
446 configImpl
.setItToBeExportedAsUri(false);
447 configImpl
.setTransient(false);
449 resourceConfig
.getAssociationConfigs().put(propertyName
, configImpl
);
451 element
.getAnnotation(Id
.class);
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
;