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 DefaultAnnotationConfigScanner registrar
;
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();
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
107 * @param resourceClass The class to scanClassForConfig retrieve configuration for
108 * @return Configuration for the resource; NULL if no resource is available
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
);
121 String packageName
= resourceClass
.getPackage().getName();
122 EximResourceConfig resourceConfig
= null;
123 if (!scannedPackages
.contains(packageName
)) {
124 resourceConfig
= scanPackage(Package
.getPackage(packageName
),
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) {
167 if (scannedClasses
.contains(probableResourceClass
)) {
168 return getConfigurations().get(probableResourceClass
);
170 Annotation annotation
= probableResourceClass
.getAnnotation(
171 ResourceDomain
.class);
172 if (annotation
== null) {
175 EximResourceConfigImpl resourceConfig
= new EximResourceConfigImpl();
176 resourceConfig
.setDomainClass(probableResourceClass
);
177 Name nameAnnotation
= (Name
) probableResourceClass
.getAnnotation(
179 if (nameAnnotation
!= null) {
180 resourceConfig
.setName(nameAnnotation
.value());
183 resourceConfig
.setName(probableResourceClass
.getName());
185 ResourceDomain domainAnnotation
= (ResourceDomain
) annotation
;
186 resourceConfig
.setAccessByPropertyEnabled(domainAnnotation
.
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
) {
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
);
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) {
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
296 protected void scanGetterMethod(final EximResourceConfigImpl resourceConfig
,
298 throws IllegalArgumentException
{
299 String methodName
= method
.getName();
300 //Ignore the getClass bean
301 if(method
.getName().equals("getClass")) {
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
,
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();
364 element
.getAnnotation(Export
.class);
365 AssociationConfigImpl configImpl
=
366 new AssociationConfigImpl();
367 configImpl
.setName(propertyName
);
368 configImpl
.setAssociationType(AssociationConfig
.AssociationType
.
369 getAssociationType(propertyType
));
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());
380 configImpl
.setItToBeExportedAsUri(false);
381 configImpl
.setTransient(false);
383 resourceConfig
.getAssociationConfigs().put(propertyName
, configImpl
);
385 element
.getAnnotation(Id
.class);
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
;