/* * Copyright 2008 Werner Guttmann * * 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.castor.jdo.jpa.info; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; import org.castor.core.annotationprocessing.AnnotationTargetException; import org.castor.core.annotationprocessing.TargetAwareAnnotationProcessingService; import org.castor.jdo.jpa.natures.JPAClassNature; import org.castor.jdo.jpa.natures.JPAFieldNature; import org.castor.jdo.jpa.processors.ReflectionsHelper; import org.exolab.castor.mapping.MappingException; /** * Uses a JPA annotated {@link Class} to build a {@link ClassInfo} and * {@link FieldInfo}s of it and parse the mapping information in them. * * @author Peter Schmidt * @since 1.3 */ public final class ClassInfoBuilder { /** * The {@link TargetAwareAnnotationProcessingService} for class related * annotations. */ private static TargetAwareAnnotationProcessingService _classAnnotationProcessingService = new JPAClassAnnotationProcessingService(); /** * The {@link TargetAwareAnnotationProcessingService} for field related * annotations. */ private static TargetAwareAnnotationProcessingService _fieldAnnotationProcessingService = new JPAFieldAnnotationProcessingService(); /** * Do not allow instances of utility classes. */ private ClassInfoBuilder() { } /** * Builds a new {@link ClassInfo} describing the given Class. Annotations * for the class and its fields are read using the * {@link TargetAwareAnnotationProcessingService}s defined by * {@link #setClassAnnotationProcessingService(TargetAwareAnnotationProcessingService)} * and * {@link #setFieldAnnotationProcessingService(TargetAwareAnnotationProcessingService)} * . The information is stored in the {@link ClassInfo} and its related * {@link FieldInfo}s. * * @param type * The Class Object representing the Class that shall be * described. * @return a new {@link ClassInfo} describing the given Class or null if the * given type was not describable. * @throws MappingException * if annotation placement is invalid (field and property access * for the same field) or if composite keys are used! */ public static ClassInfo buildClassInfo(final Class<?> type) throws MappingException { if (type == null) { throw new IllegalArgumentException("Argument type must not be null"); } /* * get and return classInfo from ClassInfoRegistry, if already generated */ ClassInfo classInfo = ClassInfoRegistry.getClassInfo(type); if (classInfo != null) { return classInfo; } if (!isDescribable(type)) { return null; } /* * create new ClassInfo and Nature */ classInfo = new ClassInfo(type); classInfo.addNature(JPAClassNature.class.getName()); JPAClassNature jpaClassNature = new JPAClassNature(classInfo); /* * process class annotations */ try { _classAnnotationProcessingService.processAnnotations(jpaClassNature, type.getAnnotations(), type); } catch (AnnotationTargetException e) { throw new MappingException( "Could not process class bound annotations for class " + type.getSimpleName(), e); } /* * process annotations for all declared (not inherited) fields => * fieldAccess. This is not supported by castor and so a mapping * exception is thrown */ for (Field field : type.getDeclaredFields()) { if (field.getAnnotations().length != 0) { if (hasJPAAnnotations(field)) { throw new MappingException( "Castor does not support field access, thus annotated fields are " + "not supported! Move annotations to the getter method of " + field.getName()); } } } /* * process annotations for all declared (not inherited) getter methods */ for (Method method : type.getDeclaredMethods()) { if (ReflectionsHelper.isGetter(method)) { if (isDescribable(type, method)) { buildFieldInfo(classInfo, method); } else { throw new MappingException( "Invalid method annotated, method is not describeable!"); } } } if (classInfo.getKeyFieldCount() > 1) { // Castor JPA does not support composite keys throw new MappingException( "Castor-JPA does not support composite keys (found in " + type.getName() + ")"); } /* * register ClassInfo in Registry */ ClassInfoRegistry.registerClassInfo(type, classInfo); return classInfo; } /** * Checks if the {@link AnnotatedElement} has any Annotations of the Package * javax.persistence (JPA related annotations). * * @param annotatedElement * The {@link AnnotatedElement} (Field, Method, Class) to be * checked * @return true if any JPA annotations are found. */ private static boolean hasJPAAnnotations(final AnnotatedElement annotatedElement) { for (Annotation annotation : annotatedElement.getAnnotations()) { Class<? extends Annotation> annotationClass = annotation.annotationType(); if (annotationClass.getPackage().equals(Package.getPackage("javax.persistence"))) { return true; } } return false; } /** * Build a {@link FieldInfo} describing the field (accessed by the given * {@link Method}) by processing its annotations and add the generated * {@link FieldInfo} to the given {@link ClassInfo} (as field or key). * * @param classInfo * the {@link ClassInfo} of the declaring class * @param method * the {@link Method} used to access (get) the underlying member. * @throws MappingException * if a FieldInfo with the same name already exists (usually * when using field AND property access). */ private static void buildFieldInfo(final ClassInfo classInfo, final Method method) throws MappingException { if (classInfo == null) { throw new IllegalArgumentException( "Argument classInfo must not be null."); } if (method == null) { throw new IllegalArgumentException( "Argument method must not be null."); } String fieldName = ReflectionsHelper.getFieldnameFromGetter(method); if (fieldName == null) { throw new IllegalArgumentException( "Can not resolve Fieldname from method name."); } Method setterMethod = null; try { setterMethod = ReflectionsHelper.getSetterMethodFromGetter(method); } catch (SecurityException e) { throw new MappingException("Setter method for field " + fieldName + " is not accessible!"); } catch (NoSuchMethodException e) { throw new MappingException("Setter method for field " + fieldName + " does not exist!", e); } Class<?> fieldType = method.getReturnType(); FieldInfo fieldInfo = new FieldInfo(classInfo, fieldType, fieldName, method, setterMethod); fieldInfo.addNature(JPAFieldNature.class.getName()); JPAFieldNature jpaFieldNature = new JPAFieldNature(fieldInfo); try { _fieldAnnotationProcessingService.processAnnotations(jpaFieldNature, method.getAnnotations(), method); } catch (AnnotationTargetException e) { throw new MappingException( "Could not process annotations for method " + method.getName(), e); } if (jpaFieldNature.isId()) { classInfo.addKey(fieldInfo); } else { classInfo.addFieldInfo(fieldInfo); } } /** * Checks whether a class is describable or not. A class is describable if it * is not Void, Object or Class and is annotated with the {@link Entity} * annotation. * * @param type * the class to check * @return false if the given type is Void, Object or Class or is not * annotated with the {@link Entity} annotation - all other Classes * are describable. */ private static boolean isDescribable(final Class<?> type) { if (Object.class.equals(type) || Void.class.equals(type) || Class.class.equals(type)) { return false; } if (type.getAnnotation(Entity.class) == null && type.getAnnotation(MappedSuperclass.class) == null ) { return false; } return true; } /** * Checks whether a {@link Method} is describable or not. A method is NOT * describable if: * <ul> * <li>The declaring Class of the method is NOT the given type (except for * methods defined by interfaces)</li> * <li>The method is synthetic</li> * <li>The method is static</li> * <li>The method is transient</li> * </ul> * * @param type * the Class the method belongs to * @param method * the {@link Method} we want to check * @return true if the method is describable, false if not. */ private static boolean isDescribable(final Class<?> type, final Method method) { boolean isDescribeable = true; Class<?> declaringClass = method.getDeclaringClass(); if ((declaringClass != null) && (!type.equals(declaringClass)) && (!declaringClass.isInterface())) { isDescribeable = false; } if (method.isSynthetic()) { isDescribeable &= false; } if (Modifier.isStatic(method.getModifiers())) { isDescribeable &= false; } if (Modifier.isTransient(method.getModifiers())) { isDescribeable &= false; } return isDescribeable; } }