001: /*
002: * Copyright 2006 Luca Garulli (luca.garulli@assetdata.it)
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.romaframework.core.schema;
018:
019: import java.io.File;
020: import java.lang.annotation.Annotation;
021: import java.lang.reflect.Field;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.HashSet;
027: import java.util.Iterator;
028: import java.util.Set;
029: import java.util.StringTokenizer;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.romaframework.aspect.core.CoreAspect;
034: import org.romaframework.aspect.core.feature.CoreClassFeatures;
035: import org.romaframework.aspect.view.ViewAspect;
036: import org.romaframework.aspect.view.feature.ViewClassFeatures;
037: import org.romaframework.core.Utility;
038: import org.romaframework.core.aspect.Aspect;
039: import org.romaframework.core.aspect.AspectManager;
040: import org.romaframework.core.config.RomaApplicationContext;
041: import org.romaframework.core.entity.ComposedEntity;
042: import org.romaframework.core.entity.ComposedEntityInstance;
043: import org.romaframework.core.exception.ConfigurationNotFoundException;
044: import org.romaframework.core.flow.ObjectContext;
045: import org.romaframework.core.schema.config.FileSystemSchemaConfiguration;
046: import org.romaframework.core.schema.config.SchemaConfiguration;
047: import org.romaframework.xml.config.XmlConfigActionType;
048: import org.romaframework.xml.config.XmlConfigClassType;
049: import org.romaframework.xml.config.XmlConfigFieldType;
050:
051: /**
052: * Represent a class. It's not necessary that a Java class exist in the Classpath since you can define a SchemaClass that inherits
053: * another Java Class and use XML descriptor to customize it. This feature avoid the writing of empty class that simply inherit real
054: * domain class.
055: *
056: * @author Luca Garulli (luca.garulli@assetdata.it)
057: */
058: public class SchemaClass extends SchemaClassDefinition {
059:
060: private String name;
061: private SchemaClass parent;
062: private Class<?> clazz;
063: private Class<?> baseClass;
064: private SchemaConfiguration descriptor;
065: private SchemaManager schemaManager;
066: private boolean reloadingStatus = false;
067: private Set<SchemaClass> subClasses;
068:
069: private static Log log = LogFactory.getLog(SchemaClass.class);
070:
071: private static final String GET_METHOD = "get";
072: private static final String IS_METHOD = "is";
073: private static final String SET_METHOD = "set";
074: private static final String ON_METHOD = "on";
075: public static final String[] IGNORE_METHOD_NAMES = { "toString",
076: "hashCode", "validate", "getClass", "on*", "clone" };
077:
078: public SchemaClass(SchemaManager iEntityManager,
079: String iEntityName, Class<?> iClass, Class<?> iBaseClass,
080: SchemaConfiguration iDescriptor)
081: throws ConfigurationNotFoundException {
082: if (iClass == null && iBaseClass == null)
083: // ERROR: CANNOT ASSOCIATE A JAVA CLASS
084: throw new ConfigurationNotFoundException("Class "
085: + iEntityName);
086:
087: schemaManager = iEntityManager;
088: clazz = iClass;
089: name = iEntityName;
090: baseClass = iBaseClass;
091:
092: if (iDescriptor != null)
093: // USE THE DESCRIPTOR AS PARAMETER (DEFINED AS INLINE)
094: descriptor = iDescriptor;
095: else {
096: descriptor = RomaApplicationContext.getInstance().getBean(
097: SchemaConfigurationLoader.class)
098: .getXmlFileSystemSchemaConfiguration(name);
099:
100: if (descriptor != null)
101: ObjectContext.getInstance().getComponent(
102: SchemaReloader.class).addResourceForReloading(
103: ((FileSystemSchemaConfiguration) descriptor)
104: .getFile(), iEntityName);
105: }
106:
107: File classFile = RomaApplicationContext.getInstance().getBean(
108: SchemaClassResolver.class).getFileOwner(
109: name + SchemaClassResolver.CLASS_SUFFIX);
110:
111: if (classFile != null)
112: ObjectContext.getInstance().getComponent(
113: SchemaReloader.class).addResourceForReloading(
114: classFile, iEntityName);
115: }
116:
117: @Override
118: public boolean equals(Object arg0) {
119: if (arg0 == null || !(arg0 instanceof SchemaClass))
120: return false;
121:
122: SchemaClass other = (SchemaClass) arg0;
123:
124: if (name != null)
125: return name.equals(other.getName());
126: return false;
127: }
128:
129: @Override
130: public int hashCode() {
131: if (name != null)
132: return name.hashCode();
133: return -1;
134: }
135:
136: public void configure() {
137: config();
138: }
139:
140: private void config() {
141: inheritBySuperClass();
142: readAllAnnotations();
143:
144: readFields();
145: readActions();
146: readEvents();
147: }
148:
149: /**
150: * Reload class configuration from file. This event is invoked when the file descriptor is changed.
151: */
152: public void signalUpdatedFile(File iFile) {
153: try {
154: log
155: .warn("[SchemaClass.signalUpdatedFile] Reloading configuration for class: '"
156: + name + "' from file: " + iFile);
157: if (subClasses != null) {
158: // IF ANY, RELOAD ALL SUB-CLASSES IN CASCADING
159: for (SchemaClass cls : subClasses)
160: cls.signalUpdatedFile(null);
161: }
162:
163: // REATTACH THE NEW CLASS LOADED
164: // Class<?> oldClass = clazz;
165: // clazz = ObjectContext.getInstance().getComponent(SchemaClassResolver.class).reloadEntityClass(clazz.getName());
166: //
167: // updateAllReferencesToOldClass(oldClass);
168:
169: // RESET CLASS INFO
170: reset();
171:
172: reloadingStatus = true;
173: if (iFile != null
174: && iFile.getName().endsWith(
175: SchemaClassResolver.DESCRIPTOR_SUFFIX))
176: descriptor.load();
177: config();
178: reloadingStatus = false;
179: } catch (Exception e) {
180: log.error("[SchemaClass.signalUpdatedFile] Error", e);
181: }
182: }
183:
184: private void updateAllReferencesToOldClass(Class<?> oldClass) {
185: int counter = 0;
186: for (SchemaClass schema : schemaManager.getAllClassesInfo()) {
187: Iterator<SchemaField> it = schema.getFieldIterator();
188: SchemaField f;
189: while (it.hasNext()) {
190: f = it.next();
191: if (f.getTypeClass() != null
192: && f.getTypeClass().equals(oldClass)) {
193: f.setTypeClass(clazz);
194: ++counter;
195: }
196: }
197: }
198: log
199: .warn("[SchemaClass.updateAllReferencesToOldClass] Total references updated: "
200: + counter);
201: }
202:
203: private void reset() {
204: allFeatures.clear();
205: fields.clear();
206: orderedFields.clear();
207: actions.clear();
208: orderedActions.clear();
209: events.clear();
210: }
211:
212: public Set<SchemaClass> getSubClasses() {
213: return subClasses;
214: }
215:
216: public Class<?> getClazz() {
217: return clazz != null ? clazz : baseClass;
218: }
219:
220: public String getName() {
221: return name;
222: }
223:
224: public SchemaClass getParent() {
225: return parent;
226: }
227:
228: public SchemaConfiguration getDescriptor() {
229: return descriptor;
230: }
231:
232: @Override
233: public SchemaClass getSchemaClass() {
234: return this ;
235: }
236:
237: public SchemaManager getSchemaManager() {
238: return schemaManager;
239: }
240:
241: private void inheritBySuperClass() {
242: Class<?> super Class = null;
243:
244: if (clazz != null)
245: // GET SUPER CLASS IF ANY
246: super Class = clazz.getSuperclass();
247: else
248: // NO CONCRETE JAVA CLASS FOUND: COPY FROM BASE CLASS
249: super Class = baseClass;
250:
251: parent = null;
252: if (super Class != null && super Class != Object.class)
253: parent = schemaManager.getClassInfo(super Class, null);
254:
255: if (parent != null) {
256: if (!reloadingStatus)
257: // ADD MYSELF AS SUB-CLASS OF MY SUPER CLASS
258: parent.addSubclass(this );
259: copyDefinition(parent);
260: }
261: }
262:
263: /**
264: * Add subclass to determine inheritance three. Useful on reloading to refresh all entity in cascading.
265: *
266: * @param iSubClassEntity
267: * SubClass Entity
268: */
269: private void addSubclass(SchemaClass iSubClassEntity) {
270: if (subClasses == null)
271: subClasses = new HashSet<SchemaClass>();
272:
273: subClasses.add(iSubClassEntity);
274: }
275:
276: private void readAllAnnotations() {
277: // BROWSE ALL ASPECTS
278: Collection<Aspect> aspects = AspectManager.getInstance()
279: .getConfigurationValues();
280:
281: String annotationName;
282: Class<? extends Annotation> annotationClass;
283: Annotation annotation;
284:
285: XmlConfigClassType parentDescriptor = null;
286:
287: if (descriptor != null)
288: parentDescriptor = descriptor.getType();
289:
290: for (Aspect aspect : aspects) {
291: // READ CLASS ANNOTATIONS
292:
293: if (clazz != null) {
294: // COMPOSE ANNOTATION NAME BY ASPECT
295: annotationName = aspect.aspectName();
296: annotationName = Character.toUpperCase(annotationName
297: .charAt(0))
298: + annotationName.substring(1) + "Class";
299:
300: // CHECK FOR ANNOTATION PRESENCE
301: try {
302: annotationClass = (Class<? extends Annotation>) Class
303: .forName(Utility.ROMA_PACKAGE + ".aspect."
304: + aspect.aspectName()
305: + ".annotation." + annotationName);
306: annotation = clazz.getAnnotation(annotationClass);
307: } catch (ClassNotFoundException e) {
308: // ANNOTATION CLASS NOT EXIST FOR CURRENT ASPECT
309: annotation = null;
310: }
311: } else
312: annotation = null;
313:
314: // READ XML ANNOTATIONS
315: aspect.configClass(this , annotation, parentDescriptor);
316: }
317: }
318:
319: private void readFields() {
320: readFields(clazz != null ? clazz : baseClass);
321: }
322:
323: private void readFields(Class<?> iClass) {
324: SchemaField fieldInfo;
325: Method[] methodArray = SchemaHelper.getMethods(iClass);
326: Method getterMethod;
327: Method setterMethod;
328: Field field;
329: String fieldName;
330: Class<?> fieldType;
331: for (int i = 0; i < methodArray.length; ++i) {
332: getterMethod = methodArray[i];
333:
334: if (Modifier.isStatic(getterMethod.getModifiers()))
335: // JUMP STATIC FIELDS
336: continue;
337:
338: if (!Modifier.isPublic(getterMethod.getModifiers()))
339: // JUMP NOT PUBLIC FIELDS
340: continue;
341:
342: fieldName = getterMethod.getName();
343:
344: int prefixLength;
345: if (fieldName.startsWith(GET_METHOD))
346: prefixLength = GET_METHOD.length();
347: else if (fieldName.startsWith(IS_METHOD))
348: prefixLength = IS_METHOD.length();
349: else
350: continue;
351:
352: if (getterMethod.getParameterTypes() != null
353: && getterMethod.getParameterTypes().length > 0)
354: continue;
355:
356: if (isToIgnoreMethod(getterMethod))
357: // IGNORE THE METHOD SINCE IT'S IN IGNORE_METHOD_NAMES
358: continue;
359:
360: if (fieldName.length() <= prefixLength)
361: // GET METHOD ONLY: JUMP IT
362: continue;
363:
364: log.debug("[SchemaClass] Class " + getName()
365: + " found field: " + fieldName);
366:
367: // GET FIELD NAME
368: fieldName = Character.toLowerCase(fieldName
369: .charAt(prefixLength))
370: + fieldName.substring(prefixLength + 1);
371:
372: // GET FIELD TYPE
373: fieldType = getterMethod.getReturnType();
374:
375: try {
376: // TRY TO FIND SETTER METHOD IF ANY
377: setterMethod = iClass.getMethod(SET_METHOD
378: + getterMethod.getName()
379: .substring(prefixLength),
380: new Class[] { fieldType });
381: } catch (Exception e) {
382: setterMethod = null;
383: }
384:
385: // TRY TO FIND FIELD IF ANY
386: field = SchemaHelper.getField(iClass, fieldName);
387: if (field != null && field.getType() != Object.class) {
388: fieldType = field.getType();
389: }
390:
391: if (getterMethod.getName().equals("getEntity")
392: && ComposedEntity.class
393: .isAssignableFrom(getterMethod
394: .getDeclaringClass())) {
395: // ENTITY FIELD: CHECK FOR SPECIAL ENTITY TYPE
396: // TODO: REMOVE THIS WIRED CONCEPT
397: Class<?> entityClass = (Class<?>) getFeatures(
398: ViewAspect.ASPECT_NAME).getAttribute(
399: ViewClassFeatures.ENTITY);
400: if (entityClass != null && entityClass != Object.class)
401: fieldType = entityClass;
402: else {
403: if (!iClass.equals(ComposedEntityInstance.class))
404: log
405: .warn("[SchemaClass.readFields] Cannot find the definition of annotation @ViewClass(entity=X.class) for class '"
406: + iClass
407: + "'. Since it's a ComposedEntityInstance implementation an annotation is expected to expand the entity correctly.");
408: }
409: }
410:
411: fieldInfo = getField(fieldName);
412: if (fieldInfo == null) {
413: // FIELD NOT EXISTENT: CREATE IT AND INSERT IN THE COLLECTION
414: fieldInfo = new SchemaField(this );
415: setField(fieldName, fieldInfo);
416: }
417:
418: // GENERATE OR OVERWRITE (IN CASE OF INHERITANCE) FIELD CONFIGURATION
419: fieldInfo.configure(RomaApplicationContext.getInstance()
420: .getBean(ViewAspect.class), fieldName, fieldType,
421: field, getterMethod, setterMethod);
422:
423: fieldInfo.setOrder(getFieldOrder(fieldInfo));
424: }
425:
426: Collections.sort(orderedFields);
427: }
428:
429: private void readActions() {
430: if (clazz == null)
431: // NO CONCRETE JAVA CLASS FOUND
432: return;
433:
434: SchemaAction actionInfo;
435: Method[] methodArray = SchemaHelper.getMethods(clazz);
436: Method currentMethod;
437: String methodName;
438: for (int i = 0; i < methodArray.length; ++i) {
439: currentMethod = methodArray[i];
440: methodName = currentMethod.getName();
441:
442: log.debug("[SchemaClass] TEMP Class " + getName()
443: + " found method: " + currentMethod);
444:
445: if (Modifier.isStatic(currentMethod.getModifiers()))
446: // JUMP STATIC METHODS
447: continue;
448:
449: if (!Modifier.isPublic(currentMethod.getModifiers()))
450: // IGNORE NON PUBLIC METHODS
451: continue;
452:
453: if (currentMethod.getParameterTypes().length > 0)
454: // IGNORE METHODS WITH PARAMETERS
455: continue;
456:
457: if (isToIgnoreMethod(currentMethod))
458: // IGNORE METHOD
459: continue;
460:
461: if (methodName.startsWith(GET_METHOD)
462: || methodName.startsWith(IS_METHOD)
463: || methodName.startsWith(SET_METHOD))
464: // GETTER OR SETTER: IGNORE IT (ARE TREATED AS FIELDS)
465: continue;
466:
467: log.debug("[SchemaClass] Class " + getName()
468: + " found method: " + methodName);
469:
470: actionInfo = getAction(methodName);
471:
472: if (actionInfo == null) {
473: // ACTION NOT EXISTENT: CREATE IT AND INSERT IN THE COLLECTION
474: actionInfo = new SchemaAction(this );
475: setAction(methodName, actionInfo);
476: }
477:
478: // GENERATE OR OVERWRITE (IN CASE OF INHERITANCE) ACTION CONFIGURATION
479: actionInfo.configure(currentMethod);
480:
481: actionInfo.setOrder(getActionOrder(actionInfo));
482: }
483:
484: Collections.sort(orderedActions);
485: }
486:
487: private void readEvents() {
488: if (clazz == null)
489: // NO CONCRETE JAVA CLASS FOUND
490: return;
491:
492: SchemaEvent eventInfo;
493: Method[] methodArray = SchemaHelper.getMethods(clazz);
494: Method eventMethod;
495: String eventName;
496: for (int i = 0; i < methodArray.length; ++i) {
497: eventMethod = methodArray[i];
498:
499: if (Modifier.isStatic(eventMethod.getModifiers()))
500: // JUMP STATIC FIELDS
501: continue;
502:
503: if (!Modifier.isPublic(eventMethod.getModifiers()))
504: // JUMP NOT PUBLIC FIELDS
505: continue;
506:
507: if (!eventMethod.getName().startsWith(ON_METHOD))
508: continue;
509:
510: // GET FIELD NAME
511: eventName = Character.toLowerCase(eventMethod.getName()
512: .charAt(ON_METHOD.length()))
513: + eventMethod.getName().substring(
514: ON_METHOD.length() + 1);
515:
516: eventInfo = getEvent(eventName);
517:
518: if (eventInfo == null) {
519: // EVENT NOT EXISTENT: CREATE IT AND INSERT IN THE COLLECTION
520: eventInfo = new SchemaEvent(this , eventName);
521: setEvent(eventName, eventInfo);
522: }
523:
524: // GENERATE OR OVERWRITE (IN CASE OF INHERITANCE) EVENT CONFIGURATION
525: eventInfo.configure(eventMethod);
526: }
527: }
528:
529: @Override
530: public String toString() {
531: return name
532: + (clazz != null ? " (class:" + clazz.getName() + ")"
533: : "");
534: }
535:
536: private short getFieldOrder(SchemaField iField) {
537: String orderedValues = (String) getFeature(
538: CoreAspect.ASPECT_NAME, CoreClassFeatures.ORDER_FIELDS);
539:
540: if (orderedValues != null) {
541: StringTokenizer tokenizer = new StringTokenizer(
542: orderedValues, " ");
543: for (short fieldNum = 0; tokenizer.hasMoreTokens(); ++fieldNum) {
544: if (tokenizer.nextToken().equals(iField.getName()))
545: // FOUND: RETURN THE ORDER
546: return fieldNum;
547: }
548: }
549:
550: if (descriptor != null && descriptor.getType() != null
551: && descriptor.getType().getFields() != null) {
552: // SEARCH FORM DEFINITION IN DESCRIPTOR
553: XmlConfigFieldType[] allFields = descriptor.getType()
554: .getFields().getFieldArray();
555: for (short fieldNum = 0; fieldNum < allFields.length; ++fieldNum) {
556: if (allFields[fieldNum].getName().equals(
557: iField.getName())) {
558: // FOUND: RETURN THE ORDER
559: return fieldNum;
560: }
561: }
562: }
563:
564: return iField.getOrder();
565: }
566:
567: private short getActionOrder(SchemaElement iAction) {
568: String orderedValues = (String) getFeature(
569: CoreAspect.ASPECT_NAME, CoreClassFeatures.ORDER_ACTIONS);
570:
571: if (orderedValues != null) {
572: StringTokenizer tokenizer = new StringTokenizer(
573: orderedValues, " ");
574: for (short actionNum = 0; tokenizer.hasMoreTokens(); ++actionNum) {
575: if (tokenizer.nextToken().equals(iAction.getName()))
576: // FOUND: RETURN THE ORDER
577: return actionNum;
578: }
579: }
580:
581: if (descriptor != null && descriptor.getType() != null
582: && descriptor.getType().getActions() != null) {
583: // SEARCH FORM DEFINITION IN DESCRIPTOR
584: XmlConfigActionType[] allActions = descriptor.getType()
585: .getActions().getActionArray();
586: for (short actionNum = 0; actionNum < allActions.length; ++actionNum) {
587: if (allActions[actionNum].getName().equals(
588: iAction.getName())) {
589: // FOUND: RETURN THE ORDER
590: return actionNum;
591: }
592: }
593: }
594: return iAction.getOrder();
595: }
596:
597: private boolean isToIgnoreMethod(Method currentMethod) {
598: String methodName = currentMethod.getName();
599: // CHECK FOR FIXED NAMES TO IGNORE
600: for (int ignoreId = 0; ignoreId < IGNORE_METHOD_NAMES.length; ++ignoreId) {
601: if (SchemaAction.ignoreMethod(
602: IGNORE_METHOD_NAMES[ignoreId], methodName))
603: return true;
604: }
605:
606: // CHECK FOR CUSTOM NAMES TO IGNORE, IF ANY
607: for (Iterator<String> it = schemaManager.getIgnoreActions()
608: .iterator(); it.hasNext();) {
609: if (SchemaAction.ignoreMethod(it.next(), methodName))
610: return true;
611: }
612:
613: return false;
614: }
615:
616: public Class<?> getBaseClass() {
617: return baseClass;
618: }
619: }
|