/*
* Copyright 2006 Luca Garulli (luca.garulli@assetdata.it)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.romaframework.core.schema;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.romaframework.aspect.core.CoreAspect;
import org.romaframework.aspect.core.feature.CoreClassFeatures;
import org.romaframework.aspect.view.ViewAspect;
import org.romaframework.aspect.view.feature.ViewClassFeatures;
import org.romaframework.core.Utility;
import org.romaframework.core.aspect.Aspect;
import org.romaframework.core.aspect.AspectManager;
import org.romaframework.core.config.RomaApplicationContext;
import org.romaframework.core.entity.ComposedEntity;
import org.romaframework.core.entity.ComposedEntityInstance;
import org.romaframework.core.exception.ConfigurationNotFoundException;
import org.romaframework.core.flow.ObjectContext;
import org.romaframework.core.schema.config.FileSystemSchemaConfiguration;
import org.romaframework.core.schema.config.SchemaConfiguration;
import org.romaframework.xml.config.XmlConfigActionType;
import org.romaframework.xml.config.XmlConfigClassType;
import org.romaframework.xml.config.XmlConfigFieldType;
/**
* Represent a class. It's not necessary that a Java class exist in the Classpath since you can define a SchemaClass that inherits
* another Java Class and use XML descriptor to customize it. This feature avoid the writing of empty class that simply inherit real
* domain class.
*
* @author Luca Garulli (luca.garulli@assetdata.it)
*/
public class SchemaClass extends SchemaClassDefinition {
private String name;
private SchemaClass parent;
private Class<?> clazz;
private Class<?> baseClass;
private SchemaConfiguration descriptor;
private SchemaManager schemaManager;
private boolean reloadingStatus = false;
private Set<SchemaClass> subClasses;
private static Log log = LogFactory.getLog(SchemaClass.class);
private static final String GET_METHOD = "get";
private static final String IS_METHOD = "is";
private static final String SET_METHOD = "set";
private static final String ON_METHOD = "on";
public static final String[] IGNORE_METHOD_NAMES = { "toString", "hashCode", "validate", "getClass", "on*", "clone" };
public SchemaClass(SchemaManager iEntityManager, String iEntityName, Class<?> iClass, Class<?> iBaseClass,
SchemaConfiguration iDescriptor) throws ConfigurationNotFoundException {
if (iClass == null && iBaseClass == null)
// ERROR: CANNOT ASSOCIATE A JAVA CLASS
throw new ConfigurationNotFoundException("Class " + iEntityName);
schemaManager = iEntityManager;
clazz = iClass;
name = iEntityName;
baseClass = iBaseClass;
if (iDescriptor != null)
// USE THE DESCRIPTOR AS PARAMETER (DEFINED AS INLINE)
descriptor = iDescriptor;
else {
descriptor = RomaApplicationContext.getInstance().getBean(SchemaConfigurationLoader.class)
.getXmlFileSystemSchemaConfiguration(name);
if (descriptor != null)
ObjectContext.getInstance().getComponent(SchemaReloader.class).addResourceForReloading(
((FileSystemSchemaConfiguration) descriptor).getFile(), iEntityName);
}
File classFile = RomaApplicationContext.getInstance().getBean(SchemaClassResolver.class).getFileOwner(
name + SchemaClassResolver.CLASS_SUFFIX);
if (classFile != null)
ObjectContext.getInstance().getComponent(SchemaReloader.class).addResourceForReloading(classFile, iEntityName);
}
@Override
public boolean equals(Object arg0) {
if (arg0 == null || !(arg0 instanceof SchemaClass))
return false;
SchemaClass other = (SchemaClass) arg0;
if (name != null)
return name.equals(other.getName());
return false;
}
@Override
public int hashCode() {
if (name != null)
return name.hashCode();
return -1;
}
public void configure() {
config();
}
private void config() {
inheritBySuperClass();
readAllAnnotations();
readFields();
readActions();
readEvents();
}
/**
* Reload class configuration from file. This event is invoked when the file descriptor is changed.
*/
public void signalUpdatedFile(File iFile) {
try {
log.warn("[SchemaClass.signalUpdatedFile] Reloading configuration for class: '" + name + "' from file: " + iFile);
if (subClasses != null) {
// IF ANY, RELOAD ALL SUB-CLASSES IN CASCADING
for (SchemaClass cls : subClasses)
cls.signalUpdatedFile(null);
}
// REATTACH THE NEW CLASS LOADED
// Class<?> oldClass = clazz;
// clazz = ObjectContext.getInstance().getComponent(SchemaClassResolver.class).reloadEntityClass(clazz.getName());
//
// updateAllReferencesToOldClass(oldClass);
// RESET CLASS INFO
reset();
reloadingStatus = true;
if (iFile != null && iFile.getName().endsWith(SchemaClassResolver.DESCRIPTOR_SUFFIX))
descriptor.load();
config();
reloadingStatus = false;
} catch (Exception e) {
log.error("[SchemaClass.signalUpdatedFile] Error", e);
}
}
private void updateAllReferencesToOldClass(Class<?> oldClass) {
int counter = 0;
for (SchemaClass schema : schemaManager.getAllClassesInfo()) {
Iterator<SchemaField> it = schema.getFieldIterator();
SchemaField f;
while (it.hasNext()) {
f = it.next();
if (f.getTypeClass() != null && f.getTypeClass().equals(oldClass)) {
f.setTypeClass(clazz);
++counter;
}
}
}
log.warn("[SchemaClass.updateAllReferencesToOldClass] Total references updated: " + counter);
}
private void reset() {
allFeatures.clear();
fields.clear();
orderedFields.clear();
actions.clear();
orderedActions.clear();
events.clear();
}
public Set<SchemaClass> getSubClasses() {
return subClasses;
}
public Class<?> getClazz() {
return clazz != null ? clazz : baseClass;
}
public String getName() {
return name;
}
public SchemaClass getParent() {
return parent;
}
public SchemaConfiguration getDescriptor() {
return descriptor;
}
@Override
public SchemaClass getSchemaClass() {
return this;
}
public SchemaManager getSchemaManager() {
return schemaManager;
}
private void inheritBySuperClass() {
Class<?> superClass = null;
if (clazz != null)
// GET SUPER CLASS IF ANY
superClass = clazz.getSuperclass();
else
// NO CONCRETE JAVA CLASS FOUND: COPY FROM BASE CLASS
superClass = baseClass;
parent = null;
if (superClass != null && superClass != Object.class)
parent = schemaManager.getClassInfo(superClass, null);
if (parent != null) {
if (!reloadingStatus)
// ADD MYSELF AS SUB-CLASS OF MY SUPER CLASS
parent.addSubclass(this);
copyDefinition(parent);
}
}
/**
* Add subclass to determine inheritance three. Useful on reloading to refresh all entity in cascading.
*
* @param iSubClassEntity
* SubClass Entity
*/
private void addSubclass(SchemaClass iSubClassEntity) {
if (subClasses == null)
subClasses = new HashSet<SchemaClass>();
subClasses.add(iSubClassEntity);
}
private void readAllAnnotations() {
// BROWSE ALL ASPECTS
Collection<Aspect> aspects = AspectManager.getInstance().getConfigurationValues();
String annotationName;
Class<? extends Annotation> annotationClass;
Annotation annotation;
XmlConfigClassType parentDescriptor = null;
if (descriptor != null)
parentDescriptor = descriptor.getType();
for (Aspect aspect : aspects) {
// READ CLASS ANNOTATIONS
if (clazz != null) {
// COMPOSE ANNOTATION NAME BY ASPECT
annotationName = aspect.aspectName();
annotationName = Character.toUpperCase(annotationName.charAt(0)) + annotationName.substring(1) + "Class";
// CHECK FOR ANNOTATION PRESENCE
try {
annotationClass = (Class<? extends Annotation>) Class.forName(Utility.ROMA_PACKAGE + ".aspect." + aspect.aspectName()
+ ".annotation." + annotationName);
annotation = clazz.getAnnotation(annotationClass);
} catch (ClassNotFoundException e) {
// ANNOTATION CLASS NOT EXIST FOR CURRENT ASPECT
annotation = null;
}
} else
annotation = null;
// READ XML ANNOTATIONS
aspect.configClass(this, annotation, parentDescriptor);
}
}
private void readFields() {
readFields(clazz != null ? clazz : baseClass);
}
private void readFields(Class<?> iClass) {
SchemaField fieldInfo;
Method[] methodArray = SchemaHelper.getMethods(iClass);
Method getterMethod;
Method setterMethod;
Field field;
String fieldName;
Class<?> fieldType;
for (int i = 0; i < methodArray.length; ++i) {
getterMethod = methodArray[i];
if (Modifier.isStatic(getterMethod.getModifiers()))
// JUMP STATIC FIELDS
continue;
if (!Modifier.isPublic(getterMethod.getModifiers()))
// JUMP NOT PUBLIC FIELDS
continue;
fieldName = getterMethod.getName();
int prefixLength;
if (fieldName.startsWith(GET_METHOD))
prefixLength = GET_METHOD.length();
else if (fieldName.startsWith(IS_METHOD))
prefixLength = IS_METHOD.length();
else
continue;
if (getterMethod.getParameterTypes() != null && getterMethod.getParameterTypes().length > 0)
continue;
if (isToIgnoreMethod(getterMethod))
// IGNORE THE METHOD SINCE IT'S IN IGNORE_METHOD_NAMES
continue;
if (fieldName.length() <= prefixLength)
// GET METHOD ONLY: JUMP IT
continue;
log.debug("[SchemaClass] Class " + getName() + " found field: " + fieldName);
// GET FIELD NAME
fieldName = Character.toLowerCase(fieldName.charAt(prefixLength)) + fieldName.substring(prefixLength + 1);
// GET FIELD TYPE
fieldType = getterMethod.getReturnType();
try {
// TRY TO FIND SETTER METHOD IF ANY
setterMethod = iClass.getMethod(SET_METHOD + getterMethod.getName().substring(prefixLength), new Class[] { fieldType });
} catch (Exception e) {
setterMethod = null;
}
// TRY TO FIND FIELD IF ANY
field = SchemaHelper.getField(iClass, fieldName);
if (field != null && field.getType() != Object.class) {
fieldType = field.getType();
}
if (getterMethod.getName().equals("getEntity") && ComposedEntity.class.isAssignableFrom(getterMethod.getDeclaringClass())) {
// ENTITY FIELD: CHECK FOR SPECIAL ENTITY TYPE
// TODO: REMOVE THIS WIRED CONCEPT
Class<?> entityClass = (Class<?>) getFeatures(ViewAspect.ASPECT_NAME).getAttribute(ViewClassFeatures.ENTITY);
if (entityClass != null && entityClass != Object.class)
fieldType = entityClass;
else {
if (!iClass.equals(ComposedEntityInstance.class))
log
.warn("[SchemaClass.readFields] Cannot find the definition of annotation @ViewClass(entity=X.class) for class '"
+ iClass
+ "'. Since it's a ComposedEntityInstance implementation an annotation is expected to expand the entity correctly.");
}
}
fieldInfo = getField(fieldName);
if (fieldInfo == null) {
// FIELD NOT EXISTENT: CREATE IT AND INSERT IN THE COLLECTION
fieldInfo = new SchemaField(this);
setField(fieldName, fieldInfo);
}
// GENERATE OR OVERWRITE (IN CASE OF INHERITANCE) FIELD CONFIGURATION
fieldInfo.configure(RomaApplicationContext.getInstance().getBean(ViewAspect.class), fieldName, fieldType, field,
getterMethod, setterMethod);
fieldInfo.setOrder(getFieldOrder(fieldInfo));
}
Collections.sort(orderedFields);
}
private void readActions() {
if (clazz == null)
// NO CONCRETE JAVA CLASS FOUND
return;
SchemaAction actionInfo;
Method[] methodArray = SchemaHelper.getMethods(clazz);
Method currentMethod;
String methodName;
for (int i = 0; i < methodArray.length; ++i) {
currentMethod = methodArray[i];
methodName = currentMethod.getName();
log.debug("[SchemaClass] TEMP Class " + getName() + " found method: " + currentMethod);
if (Modifier.isStatic(currentMethod.getModifiers()))
// JUMP STATIC METHODS
continue;
if (!Modifier.isPublic(currentMethod.getModifiers()))
// IGNORE NON PUBLIC METHODS
continue;
if (currentMethod.getParameterTypes().length > 0)
// IGNORE METHODS WITH PARAMETERS
continue;
if (isToIgnoreMethod(currentMethod))
// IGNORE METHOD
continue;
if (methodName.startsWith(GET_METHOD) || methodName.startsWith(IS_METHOD) || methodName.startsWith(SET_METHOD))
// GETTER OR SETTER: IGNORE IT (ARE TREATED AS FIELDS)
continue;
log.debug("[SchemaClass] Class " + getName() + " found method: " + methodName);
actionInfo = getAction(methodName);
if (actionInfo == null) {
// ACTION NOT EXISTENT: CREATE IT AND INSERT IN THE COLLECTION
actionInfo = new SchemaAction(this);
setAction(methodName, actionInfo);
}
// GENERATE OR OVERWRITE (IN CASE OF INHERITANCE) ACTION CONFIGURATION
actionInfo.configure(currentMethod);
actionInfo.setOrder(getActionOrder(actionInfo));
}
Collections.sort(orderedActions);
}
private void readEvents() {
if (clazz == null)
// NO CONCRETE JAVA CLASS FOUND
return;
SchemaEvent eventInfo;
Method[] methodArray = SchemaHelper.getMethods(clazz);
Method eventMethod;
String eventName;
for (int i = 0; i < methodArray.length; ++i) {
eventMethod = methodArray[i];
if (Modifier.isStatic(eventMethod.getModifiers()))
// JUMP STATIC FIELDS
continue;
if (!Modifier.isPublic(eventMethod.getModifiers()))
// JUMP NOT PUBLIC FIELDS
continue;
if (!eventMethod.getName().startsWith(ON_METHOD))
continue;
// GET FIELD NAME
eventName = Character.toLowerCase(eventMethod.getName().charAt(ON_METHOD.length()))
+ eventMethod.getName().substring(ON_METHOD.length() + 1);
eventInfo = getEvent(eventName);
if (eventInfo == null) {
// EVENT NOT EXISTENT: CREATE IT AND INSERT IN THE COLLECTION
eventInfo = new SchemaEvent(this, eventName);
setEvent(eventName, eventInfo);
}
// GENERATE OR OVERWRITE (IN CASE OF INHERITANCE) EVENT CONFIGURATION
eventInfo.configure(eventMethod);
}
}
@Override
public String toString() {
return name + (clazz != null ? " (class:" + clazz.getName() + ")" : "");
}
private short getFieldOrder(SchemaField iField) {
String orderedValues = (String) getFeature(CoreAspect.ASPECT_NAME, CoreClassFeatures.ORDER_FIELDS);
if (orderedValues != null) {
StringTokenizer tokenizer = new StringTokenizer(orderedValues, " ");
for (short fieldNum = 0; tokenizer.hasMoreTokens(); ++fieldNum) {
if (tokenizer.nextToken().equals(iField.getName()))
// FOUND: RETURN THE ORDER
return fieldNum;
}
}
if (descriptor != null && descriptor.getType() != null && descriptor.getType().getFields() != null) {
// SEARCH FORM DEFINITION IN DESCRIPTOR
XmlConfigFieldType[] allFields = descriptor.getType().getFields().getFieldArray();
for (short fieldNum = 0; fieldNum < allFields.length; ++fieldNum) {
if (allFields[fieldNum].getName().equals(iField.getName())) {
// FOUND: RETURN THE ORDER
return fieldNum;
}
}
}
return iField.getOrder();
}
private short getActionOrder(SchemaElement iAction) {
String orderedValues = (String) getFeature(CoreAspect.ASPECT_NAME, CoreClassFeatures.ORDER_ACTIONS);
if (orderedValues != null) {
StringTokenizer tokenizer = new StringTokenizer(orderedValues, " ");
for (short actionNum = 0; tokenizer.hasMoreTokens(); ++actionNum) {
if (tokenizer.nextToken().equals(iAction.getName()))
// FOUND: RETURN THE ORDER
return actionNum;
}
}
if (descriptor != null && descriptor.getType() != null && descriptor.getType().getActions() != null) {
// SEARCH FORM DEFINITION IN DESCRIPTOR
XmlConfigActionType[] allActions = descriptor.getType().getActions().getActionArray();
for (short actionNum = 0; actionNum < allActions.length; ++actionNum) {
if (allActions[actionNum].getName().equals(iAction.getName())) {
// FOUND: RETURN THE ORDER
return actionNum;
}
}
}
return iAction.getOrder();
}
private boolean isToIgnoreMethod(Method currentMethod) {
String methodName = currentMethod.getName();
// CHECK FOR FIXED NAMES TO IGNORE
for (int ignoreId = 0; ignoreId < IGNORE_METHOD_NAMES.length; ++ignoreId) {
if (SchemaAction.ignoreMethod(IGNORE_METHOD_NAMES[ignoreId], methodName))
return true;
}
// CHECK FOR CUSTOM NAMES TO IGNORE, IF ANY
for (Iterator<String> it = schemaManager.getIgnoreActions().iterator(); it.hasNext();) {
if (SchemaAction.ignoreMethod(it.next(), methodName))
return true;
}
return false;
}
public Class<?> getBaseClass() {
return baseClass;
}
}
|