blob: ec1d8c08f1966d00836b4e10b1a26b0443ab171c [file] [log] [blame]
/**
* 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));
}
}
}
}
}