| /** |
| * Copyright 2009-2013 Oy Vaadin Ltd |
| * |
| * 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.eclipse.osbp.dsl.dto.lib.services.jpa.metadata; |
| |
| import java.beans.Introspector; |
| import java.lang.reflect.AccessibleObject; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.persistence.ElementCollection; |
| import javax.persistence.Embeddable; |
| import javax.persistence.Embedded; |
| import javax.persistence.EmbeddedId; |
| import javax.persistence.Entity; |
| import javax.persistence.Id; |
| import javax.persistence.ManyToMany; |
| import javax.persistence.ManyToOne; |
| import javax.persistence.MappedSuperclass; |
| import javax.persistence.OneToMany; |
| import javax.persistence.OneToOne; |
| import javax.persistence.Transient; |
| import javax.persistence.Version; |
| |
| import org.eclipse.osbp.dsl.dto.lib.services.jpa.metadata.PersistentPropertyMetadata.AccessType; |
| |
| /** |
| * Factory for creating and populating {@link ClassMetadata} and |
| * {@link EntityClassMetadata} instances. |
| * |
| * @author Petter Holmström (Vaadin Ltd) |
| * @since 1.0 |
| */ |
| public class MetadataFactory { |
| |
| private static MetadataFactory INSTANCE; |
| private Map<Class<?>, ClassMetadata<?>> metadataMap = new HashMap<Class<?>, ClassMetadata<?>>(); |
| |
| protected MetadataFactory() { |
| // NOP |
| } |
| |
| /** |
| * Gets the singleton instance of this factory. |
| * |
| * @return the factory instance (never null). |
| */ |
| public static MetadataFactory getInstance() { |
| if (INSTANCE == null) { |
| INSTANCE = new MetadataFactory(); |
| } |
| return INSTANCE; |
| } |
| |
| /** |
| * Extracts the entity class metadata from <code>mappedClass</code>. The |
| * access type (field or method) will be determined from the location of the |
| * {@link Id} or {@link EmbeddedId} annotation. If both of these are |
| * missing, this method will fail. This method will also fail if |
| * <code>mappedClass</code> lacks the {@link Entity} annotation. |
| * |
| * @param mappedClass |
| * the mapped class (must not be null). |
| * @return the class metadata. |
| * @throws IllegalArgumentException |
| * if no metadata could be extracted. |
| */ |
| public <T> EntityClassMetadata<T> getEntityClassMetadata( |
| Class<T> mappedClass) throws IllegalArgumentException { |
| assert mappedClass != null : "mappedClass must not be null"; |
| if (mappedClass.getAnnotation(Entity.class) == null) { |
| throw new IllegalArgumentException("The class is not an entity"); |
| } |
| PersistentPropertyMetadata.AccessType accessType = determineAccessType(mappedClass); |
| if (accessType == null) { |
| throw new IllegalArgumentException( |
| "The access type could not be determined"); |
| } else { |
| return (EntityClassMetadata<T>) getClassMetadata(mappedClass, |
| accessType); |
| } |
| } |
| |
| /** |
| * Extracts the class metadata from <code>mappedClass</code>. If |
| * <code>mappedClass</code> is {@link Embeddable}, the result will be an |
| * instance of {@link ClassMetadata}. If <code>mappedClass</code> is an |
| * {@link Entity}, the result will be an instance of |
| * {@link EntityClassMetadata}. |
| * <p> |
| * <code>accessType</code> instructs the factory where to look for |
| * annotations and which defaults to assume if there are no annotations. |
| * |
| * @param mappedClass |
| * the mapped class (must not be null). |
| * @param accessType |
| * the location where to look for annotations (must not be null). |
| * @return the class metadata. |
| * @throws IllegalArgumentException |
| * if no metadata could be extracted. |
| */ |
| @SuppressWarnings("unchecked") |
| public <T> ClassMetadata<T> getClassMetadata(Class<T> mappedClass, |
| PersistentPropertyMetadata.AccessType accessType) |
| throws IllegalArgumentException { |
| assert mappedClass != null : "mappedClass must not be null"; |
| assert accessType != null : "accessType must not be null"; |
| |
| // Check if we already have the metadata in cache |
| ClassMetadata<T> metadata = (ClassMetadata<T>) metadataMap |
| .get(mappedClass); |
| if (metadata != null) { |
| return metadata; |
| } |
| |
| // Check if we are dealing with an entity class or an embeddable class |
| Entity entity = mappedClass.getAnnotation(Entity.class); |
| Embeddable embeddable = mappedClass.getAnnotation(Embeddable.class); |
| if (entity != null) { |
| // We have an entity class |
| String entityName = entity.name().length() == 0 ? mappedClass |
| .getSimpleName() : entity.name(); |
| metadata = new EntityClassMetadata<T>(mappedClass, entityName); |
| // Put the metadata instance in the cache in case it is referenced |
| // from loadProperties() |
| metadataMap.put(mappedClass, metadata); |
| loadProperties(mappedClass, metadata, accessType); |
| |
| // Locate the version and identifier properties |
| EntityClassMetadata<T> entityMetadata = (EntityClassMetadata<T>) metadata; |
| for (PersistentPropertyMetadata pm : entityMetadata |
| .getPersistentProperties()) { |
| |
| if (pm.getAnnotation(Version.class) != null) { |
| entityMetadata.setVersionPropertyName(pm.getName()); |
| } else if (pm.getAnnotation(Id.class) != null |
| || pm.getAnnotation(EmbeddedId.class) != null) { |
| entityMetadata.setIdentifierPropertyName(pm.getName()); |
| } |
| if (entityMetadata.hasIdentifierProperty() |
| && entityMetadata.hasVersionProperty()) { |
| // No use continuing the loop if both the version |
| // and the identifier property have already been found. |
| break; |
| } |
| } |
| } else if (embeddable != null) { |
| // We have an embeddable class |
| metadata = new ClassMetadata<T>(mappedClass); |
| // Put the metadata instance in the cache in case it is referenced |
| // from loadProperties() |
| metadataMap.put(mappedClass, metadata); |
| loadProperties(mappedClass, metadata, accessType); |
| } else { |
| throw new IllegalArgumentException("The class " |
| + mappedClass.getName() |
| + " is nether an entity nor embeddable"); |
| } |
| |
| return metadata; |
| } |
| |
| protected void loadProperties(Class<?> type, ClassMetadata<?> metadata, |
| PersistentPropertyMetadata.AccessType accessType) { |
| |
| // Also check superclass for metadata |
| Class<?> superclass = type.getSuperclass(); |
| if (superclass != null |
| && (superclass.getAnnotation(MappedSuperclass.class) != null |
| || superclass.getAnnotation(Entity.class) != null ) |
| //TODO: check the change made for SONAR !!! |
| || (superclass != null |
| && superclass.getAnnotation(Embeddable.class) != null) |
| ) { |
| loadProperties(superclass, metadata, accessType); |
| } |
| |
| if (accessType == PersistentPropertyMetadata.AccessType.FIELD) { |
| extractPropertiesFromFields(type, metadata); |
| } else { |
| extractPropertiesFromMethods(type, metadata); |
| } |
| } |
| |
| protected PersistentPropertyMetadata.AccessType determineAccessType( |
| Class<?> type) { |
| // Start by looking for annotated fields |
| for (Field f : type.getDeclaredFields()) { |
| if (f.getAnnotation(Id.class) != null |
| || f.getAnnotation(EmbeddedId.class) != null) { |
| return AccessType.FIELD; |
| } |
| } |
| |
| // Then look for annotated getter methods |
| for (Method m : type.getDeclaredMethods()) { |
| if (m.getAnnotation(Id.class) != null |
| || m.getAnnotation(EmbeddedId.class) != null) { |
| return AccessType.METHOD; |
| } |
| } |
| |
| // Nothing found? Try with the superclass! |
| Class<?> superclass = type.getSuperclass(); |
| if (superclass != null |
| && (superclass.getAnnotation(MappedSuperclass.class) != null || superclass |
| .getAnnotation(Entity.class) != null)) { |
| return determineAccessType(superclass); |
| } |
| |
| // The access type could not be determined; |
| return null; |
| } |
| |
| protected boolean isReference(AccessibleObject ab) { |
| return ab.getAnnotation(ManyToOne.class) != null; |
| } |
| |
| private boolean isOneToOne(AccessibleObject ab) { |
| return ab.getAnnotation(OneToOne.class) != null; |
| } |
| |
| protected boolean isCollection(AccessibleObject ab) { |
| return ab.getAnnotation(OneToMany.class) != null; |
| } |
| |
| private boolean isManyToMany(AccessibleObject ab) { |
| return ab.getAnnotation(ManyToMany.class) != null; |
| } |
| |
| protected boolean isEmbedded(AccessibleObject ab) { |
| return (ab.getAnnotation(Embedded.class) != null || ab |
| .getAnnotation(EmbeddedId.class) != null); |
| } |
| |
| protected void extractPropertiesFromFields(Class<?> type, |
| ClassMetadata<?> metadata) { |
| for (Field f : type.getDeclaredFields()) { |
| int mod = f.getModifiers(); |
| if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) |
| && !Modifier.isTransient(mod) |
| && f.getAnnotation(Transient.class) == null) { |
| Class<?> fieldType = getFieldType(f); |
| Method setterMethod = null; |
| try { |
| final String assumedSetterName = "set" |
| + f.getName().substring(0, 1).toUpperCase() |
| + f.getName().substring(1); |
| Method method = type |
| .getMethod(assumedSetterName, fieldType); |
| setterMethod = method; |
| } catch (Exception e) { |
| // Setter does not exist or is not accessible |
| } |
| |
| if (isEmbedded(f)) { |
| ClassMetadata<?> cm = getClassMetadata(fieldType, |
| AccessType.FIELD); |
| metadata.addProperties(new PersistentPropertyMetadata(f |
| .getName(), cm, PropertyKind.EMBEDDED, f, |
| setterMethod)); |
| } else if (isReference(f)) { |
| ClassMetadata<?> cm = getClassMetadata(fieldType, |
| AccessType.FIELD); |
| metadata.addProperties(new PersistentPropertyMetadata(f |
| .getName(), cm, PropertyKind.MANY_TO_ONE, f, |
| setterMethod)); |
| } else if (isOneToOne(f)) { |
| ClassMetadata<?> cm = getClassMetadata(fieldType, |
| AccessType.FIELD); |
| metadata.addProperties(new PersistentPropertyMetadata(f |
| .getName(), cm, PropertyKind.ONE_TO_ONE, f, |
| setterMethod)); |
| } else if (isCollection(f)) { |
| metadata.addProperties(new PersistentPropertyMetadata(f |
| .getName(), fieldType, PropertyKind.ONE_TO_MANY, f, |
| setterMethod)); |
| } else if (isManyToMany(f)) { |
| metadata.addProperties(new PersistentPropertyMetadata(f |
| .getName(), fieldType, PropertyKind.MANY_TO_MANY, |
| f, setterMethod)); |
| } else if (isElementCollection(f)) { |
| metadata.addProperties(new PersistentPropertyMetadata(f |
| .getName(), fieldType, |
| PropertyKind.ELEMENT_COLLECTION, f, setterMethod)); |
| } else { |
| metadata.addProperties(new PersistentPropertyMetadata(f |
| .getName(), convertPrimitiveType(fieldType), |
| PropertyKind.SIMPLE, f, setterMethod)); |
| } |
| } |
| } |
| // Find the transient properties |
| for (Method m : type.getDeclaredMethods()) { |
| int mod = m.getModifiers(); |
| // Synthetic methods are excluded (#4590). |
| // In theory, this could filter out too much in some special cases, |
| // in which case the subclass could re-declare the accessor methods |
| // with the correct annotations as a workaround. |
| if (m.getName().startsWith("get") && m.getName().length() > 3 |
| && !Modifier.isStatic(mod) && !m.isSynthetic() |
| && m.getReturnType() != Void.TYPE |
| && m.getParameterTypes().length == 0) { |
| Method setter = null; |
| try { |
| // Check if we have a setter |
| setter = type.getDeclaredMethod("set" |
| + m.getName().substring(3), m.getReturnType()); |
| } catch (NoSuchMethodException ignoreit) { |
| } |
| String name = Introspector.decapitalize(m.getName() |
| .substring(3)); |
| |
| if (metadata.getProperty(name) == null) { |
| // No previous property has been added with the same name |
| metadata.addProperties(new PropertyMetadata(name, m |
| .getReturnType(), m, setter)); |
| } |
| } |
| } |
| } |
| |
| protected boolean isElementCollection(AccessibleObject ab) { |
| return (ab.getAnnotation(ElementCollection.class) != null); |
| } |
| |
| /** |
| * Finds the actual pointed-to type of the field. The concrete type may be |
| * other than the declared type if the targetEntity parameter is specified |
| * in certain annotations. |
| * |
| * @param f |
| * the field. |
| * @return the type of the field. |
| */ |
| private Class<?> getFieldType(Field f) { |
| Class<?> targetEntity = void.class; |
| if (isReference(f)) { |
| targetEntity = f.getAnnotation(ManyToOne.class).targetEntity(); |
| } else if (isOneToOne(f)) { |
| targetEntity = f.getAnnotation(OneToOne.class).targetEntity(); |
| } |
| if (targetEntity != void.class) { |
| return targetEntity; |
| } |
| return f.getType(); |
| } |
| |
| private Class<?> convertPrimitiveType(Class<?> type) { |
| // Vaadin fields don't work with primitive values, use wrapper types for |
| // primitives |
| if (type.isPrimitive()) { |
| if (type.equals(Boolean.TYPE)) { |
| type = Boolean.class; |
| } else if (type.equals(Integer.TYPE)) { |
| type = Integer.class; |
| } else if (type.equals(Float.TYPE)) { |
| type = Float.class; |
| } else if (type.equals(Double.TYPE)) { |
| type = Double.class; |
| } else if (type.equals(Byte.TYPE)) { |
| type = Byte.class; |
| } else if (type.equals(Character.TYPE)) { |
| type = Character.class; |
| } else if (type.equals(Short.TYPE)) { |
| type = Short.class; |
| } else if (type.equals(Long.TYPE)) { |
| type = Long.class; |
| } |
| } |
| return type; |
| } |
| |
| protected void extractPropertiesFromMethods(Class<?> type, |
| ClassMetadata<?> metadata) { |
| for (Method m : type.getDeclaredMethods()) { |
| int mod = m.getModifiers(); |
| // Synthetic methods are excluded (#4590) - otherwise you could e.g. |
| // have a synthetic and a concrete id getter (with different |
| // declared return types) in TestClasses.Integer_ConcreteId_M, and |
| // the synthetic method could override the concrete one and its |
| // annotations. |
| // In theory, this could filter out too much in some special cases, |
| // in which case the subclass could re-declare the accessor methods |
| // with the correct annotations as a workaround. |
| if (m.getName().startsWith("get") && m.getName().length() > 3 |
| && !Modifier.isStatic(mod) && !m.isSynthetic() |
| && m.getReturnType() != Void.TYPE |
| && m.getParameterTypes().length == 0) { |
| Method setter = null; |
| try { |
| // Check if we have a setter |
| setter = type.getDeclaredMethod("set" |
| + m.getName().substring(3), m.getReturnType()); |
| } catch (NoSuchMethodException ignoreit) { |
| // No setter <=> transient property |
| } |
| String name = Introspector.decapitalize(m.getName() |
| .substring(3)); |
| |
| if (setter != null && m.getAnnotation(Transient.class) == null) { |
| // Persistent property |
| if (isEmbedded(m)) { |
| ClassMetadata<?> cm = getClassMetadata( |
| m.getReturnType(), AccessType.METHOD); |
| metadata.addProperties(new PersistentPropertyMetadata( |
| name, cm, PropertyKind.EMBEDDED, m, setter)); |
| } else if (isReference(m)) { |
| ClassMetadata<?> cm = getClassMetadata( |
| m.getReturnType(), AccessType.METHOD); |
| metadata.addProperties(new PersistentPropertyMetadata( |
| name, cm, PropertyKind.MANY_TO_ONE, m, setter)); |
| } else if (isOneToOne(m)) { |
| ClassMetadata<?> cm = getClassMetadata( |
| m.getReturnType(), AccessType.METHOD); |
| metadata.addProperties(new PersistentPropertyMetadata( |
| name, cm, PropertyKind.ONE_TO_ONE, m, setter)); |
| } else if (isCollection(m)) { |
| metadata.addProperties(new PersistentPropertyMetadata( |
| name, m.getReturnType(), |
| PropertyKind.ONE_TO_MANY, m, setter)); |
| } else if (isManyToMany(m)) { |
| metadata.addProperties(new PersistentPropertyMetadata( |
| name, m.getReturnType(), |
| PropertyKind.MANY_TO_MANY, m, setter)); |
| } else if (isElementCollection(m)) { |
| metadata.addProperties(new PersistentPropertyMetadata( |
| name, m.getReturnType(), |
| PropertyKind.ELEMENT_COLLECTION, m, setter)); |
| } else { |
| metadata.addProperties(new PersistentPropertyMetadata( |
| name, m.getReturnType(), PropertyKind.SIMPLE, |
| m, setter)); |
| } |
| } else { |
| // Transient property |
| metadata.addProperties(new PropertyMetadata(name, m |
| .getReturnType(), m, setter)); |
| } |
| } |
| } |
| } |
| } |