blob: 4418e08a07ef0028d3b531b2bee31a025ea4f211 [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.jpa.services.metadata;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;
/**
* This class provides a way of accessing the JPA mapping metadata of
* <code>Entity</code> and <code>Embeddable</code> classes. This information may be used
* to construct queries or decide whether a property is sortable or not.
*
* see EntityClassMetadata
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public class ClassMetadata<T> implements Serializable {
private static final long serialVersionUID = 2569781449737488799L;
private final Class<T> mappedClass;
private final Map<String, PropertyMetadata> allProperties = new LinkedHashMap<String, PropertyMetadata>();
private final Map<String, PersistentPropertyMetadata> persistentProperties = new LinkedHashMap<String, PersistentPropertyMetadata>();
/**
* Constructs a new <code>ClassMetadata</code> instance. Properties can be
* added using the
* {@link #addProperties(org.eclipse.osbp.jpa.services.metadata.vaadin.addons.jpacontainer.metadata.PropertyMetadata[]) }
* method.
*
* @param mappedClass
* the mapped class (must not be null).
*/
protected ClassMetadata(Class<T> mappedClass) {
assert mappedClass != null : "mappedClass must not be null";
this.mappedClass = mappedClass;
}
/**
* Adds the specified property metadata to the class. Any existing
* properties with the same names will be overwritten.
*
* @param properties
* an array of properties to add.
*/
final void addProperties(PropertyMetadata... properties) {
assert properties != null : "properties must not be null";
for (PropertyMetadata pm : properties) {
allProperties.put(pm.getName(), pm);
if (pm instanceof PersistentPropertyMetadata) {
persistentProperties.put(pm.getName(),
(PersistentPropertyMetadata) pm);
} else {
// If we have a previous property and want to overwrite
// it with another that is not persistent
persistentProperties.remove(pm.getName());
}
}
}
/**
* Gets the mapped class.
*
* @return the class (never null).
*/
public Class<T> getMappedClass() {
return mappedClass;
}
/**
* Gets all the persistent properties of the class.
*
* @return an unmodifiable collection of property metadata.
*/
public Collection<PersistentPropertyMetadata> getPersistentProperties() {
return Collections
.unmodifiableCollection(persistentProperties.values());
}
/**
* Gets the names of all persistent properties of this class.
*
* @see #getPersistentProperties()
* @return an unmodifiable collection of property names.
*/
public Collection<String> getPersistentPropertyNames() {
return Collections
.unmodifiableCollection(persistentProperties.keySet());
}
/**
* Gets all the properties of the class. In addition to the persistent
* properties, all public JavaBean properties are also included (even those
* who do not have setter methods).
*
* @return an unmodifiable collection of property metadata.
*/
public Collection<PropertyMetadata> getProperties() {
return Collections.unmodifiableCollection(allProperties.values());
}
/**
* Gets the names of all the properties of this class.
*
* @see #getProperties()
* @return an unmodifiable collection of property names.
*/
public Collection<String> getPropertyNames() {
return Collections.unmodifiableCollection(allProperties.keySet());
}
/**
* Gets the metadata of the named property.
*
* @param propertyName
* the name of the property (must not be null).
* @return the property metadata, or null if not found.
*/
public PropertyMetadata getProperty(String propertyName) {
return allProperties.get(propertyName);
}
/**
* Gets the value of <code>property</code> from <code>object</code>.
*
* @param object
* the object from which the property will be retrieved (must not
* be null).
* @param property
* the metadata of the property (must not be null).
* @return the property value.
* @throws IllegalArgumentException
* if the property could not be retrieved.
*/
protected Object getPropertyValue(T object, PropertyMetadata property)
throws IllegalArgumentException { // NOSONAR
assert object != null : "object must not be null";
assert property != null : "property must not be null";
try {
if (property instanceof PersistentPropertyMetadata) {
PersistentPropertyMetadata ppmd = (PersistentPropertyMetadata) property;
if (ppmd.field != null) {
return getPropertyValueFromField(object, ppmd);
}
}
return property.getter.invoke(object);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(
"Cannot access the property value", e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException(
"Cannot access the property value", e);
}
}
private Object getPropertyValueFromField(T object,
PersistentPropertyMetadata ppmd) throws IllegalAccessException,
IllegalArgumentException, InvocationTargetException { // NOSONAR
// First we try to find a getter for the field in order to
// make getter-based lazy loading work.
Class<?> clazz = ppmd.field.getDeclaringClass();
Method getter = null;
try {
getter = clazz.getMethod("get" + capitalize(ppmd.fieldName));
} catch (Exception e) { // NOSONAR
try {
getter = clazz.getMethod("is" + capitalize(ppmd.fieldName));
} catch (Exception e1) { // NOSONAR
}
}
if (getter == null) {
try {
ppmd.field.setAccessible(true);
return ppmd.field.get(object);
} finally {
ppmd.field.setAccessible(false);
}
}
return getter.invoke(object);
}
private String capitalize(String string) {
return string.substring(0, 1).toUpperCase() + string.substring(1);
}
/**
* Sets the value of <code>property</code> to <code>value</code> on
* <code>object</code>.
*
* @param object
* the object to which the property will be set (must not be
* null).
* @param property
* the metadata of the property (must not be null).
* @param value
* the property value.
* @throws IllegalArgumentException
* if the property could not be set.
*/
protected void setPropertyValue(T object, PropertyMetadata property,
Object value) throws IllegalArgumentException { // NOSONAR
assert object != null : "object must not be null";
assert property != null : "property must not be null";
if (property != null && property.isWritable()) {
try {
if (property.setter == null
&& property instanceof PersistentPropertyMetadata) {
// use direct field access of PersistentPropertyMetadata iff
// the setter method does not exist
PersistentPropertyMetadata ppmd = (PersistentPropertyMetadata) property;
if (ppmd.field != null) {
try {
ppmd.field.setAccessible(true);
ppmd.field.set(object, value);
return;
} finally {
ppmd.field.setAccessible(false);
}
}
}
property.setter.invoke(object, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(
"Cannot set the property value", e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException(
"Cannot set the property value", e);
}
} else {
throw new IllegalArgumentException("No such writable property: "
+ (property != null?property.getName():"(property == null)") );
}
}
/**
* Gets the getter method for <code>propertyName</code> from
* <code>parent</code>.
*
* @param propertyName
* the JavaBean property name (must not be null).
* @param parent
* the class from which to get the getter method (must not be
* null).
* @return the getter method, or null if not found.
*/
protected Method getGetterMethod(String propertyName, Class<?> parent) {
String methodName = "get" + propertyName.substring(0, 1).toUpperCase()
+ propertyName.substring(1);
try {
Method m = parent.getMethod(methodName);
if (m.getReturnType() != Void.TYPE) {
return m;
} else {
return null;
}
} catch (NoSuchMethodException e) { // NOSONAR
return null;
}
}
/**
* Gets the setter method for <code>propertyName</code> from
* <code>parent</code>.
*
* @param propertyName
* the JavaBean property name (must not be null).
* @param parent
* the class from which to get the setter method (must not be
* null).
* @param propertyType
* the type of the property (must not be null).
* @return the setter method, or null if not found.
*/
protected Method getSetterMethod(String propertyName, Class<?> parent,
Class<?> propertyType) {
String methodName = "set" + propertyName.substring(0, 1).toUpperCase()
+ propertyName.substring(1);
try {
Method m = parent.getMethod(methodName, propertyType);
if (m.getReturnType() == Void.TYPE) {
return m;
} else {
return null;
}
} catch (NoSuchMethodException e) { // NOSONAR
return null;
}
}
/**
* Gets the value of <code>object.propertyName</code>. The property name may
* be nested.
*
* @param object
* the entity object from which the property value should be
* fetched (must not be null).
* @param propertyName
* the name of the property (must not be null).
* @return the property value.
* @throws IllegalArgumentException
* if the property value could not be fetched, e.g. due to
* <code>propertyName</code> being invalid.
*/
@SuppressWarnings("unchecked")
public Object getPropertyValue(T object, String propertyName)
throws IllegalArgumentException { // NOSONAR
assert object != null : "object must not be null";
assert propertyName != null : "propertyName must not be null";
StringTokenizer st = new StringTokenizer(propertyName, ".");
ClassMetadata<Object> typeMetadata = (ClassMetadata<Object>) this;
Class<?> type = null;
Object currentObject = object;
while (st.hasMoreTokens()) {
String propName = st.nextToken();
if (typeMetadata != null) {
PropertyMetadata pmd = typeMetadata.getProperty(propName);
if (pmd == null) {
throw new IllegalArgumentException("Invalid property name");
}
currentObject = typeMetadata.getPropertyValue(currentObject,
pmd);
if (currentObject == null) {
return null;
}
if (pmd instanceof PersistentPropertyMetadata) {
typeMetadata = (ClassMetadata<Object>) ((PersistentPropertyMetadata) pmd)
.getTypeMetadata();
} else {
typeMetadata = null;
}
if (typeMetadata == null) {
type = pmd.getType();
} else {
type = null;
}
} else if (type != null) {
Method getter = getGetterMethod(propName, type);
if (getter == null) {
throw new IllegalArgumentException("Invalid property name");
}
try {
currentObject = getter.invoke(currentObject);
if (currentObject == null) {
return null;
}
type = getter.getReturnType();
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not access a nested property", e);
}
}
}
return currentObject;
}
/**
* Sets the value of <code>object.propertyName</code> to <code>value</code>.
* The property name may be nested.
*
* @param object
* the object whose property should be set (must not be null).
* @param propertyName
* the name of the property to set (must not be null).
* @param value
* the value to set.
* @throws IllegalArgumentException
* if the value could not be set, e.g. due to
* <code>propertyName</code> being invalid or the property being
* read only.
* @throws IllegalStateException
* if a nested property name is used and one of the nested
* properties (other than the last one) is null.
*/
@SuppressWarnings("unchecked")
public void setPropertyValue(T object, String propertyName, Object value)
throws IllegalArgumentException, IllegalStateException { // NOSONAR
assert object != null : "object must not be null";
assert propertyName != null : "propertyName must not be null";
StringTokenizer st = new StringTokenizer(propertyName, ".");
ClassMetadata<Object> typeMetadata = (ClassMetadata<Object>) this;
Class<?> type = null;
Object currentObject = object;
while (st.hasMoreTokens()) {
String propName = st.nextToken();
if (typeMetadata != null) {
PropertyMetadata pmd = typeMetadata.getProperty(propName);
if (pmd == null) {
throw new IllegalArgumentException("Invalid property name");
}
if (!st.hasMoreTokens()) {
// We have reached the end of the chain
typeMetadata.setPropertyValue(currentObject, pmd, value);
} else {
currentObject = typeMetadata.getPropertyValue(
currentObject, pmd);
if (currentObject == null) {
throw new IllegalStateException(
"A null value was found in the chain of nested properties");
}
if (pmd instanceof PersistentPropertyMetadata) {
typeMetadata = (ClassMetadata<Object>) ((PersistentPropertyMetadata) pmd)
.getTypeMetadata();
} else {
typeMetadata = null;
}
if (typeMetadata == null) {
type = pmd.getType();
} else {
type = null;
}
}
} else if (type != null) {
Method getter = getGetterMethod(propName, type);
if (getter == null) {
throw new IllegalArgumentException("Invalid property name");
}
if (!st.hasMoreTokens()) {
// We have reached the end of the chain
Method setter = getSetterMethod(propName, type,
getter.getReturnType());
if (setter == null) {
throw new IllegalArgumentException(
"Property is read only");
}
try {
setter.invoke(currentObject, value);
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not set the value");
}
} else {
try {
currentObject = getter.invoke(currentObject);
if (currentObject == null) {
throw new IllegalStateException(
"A null value was found in the chain of nested properties");
}
type = getter.getReturnType();
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not access a nested property", e);
}
}
}
}
}
@Override
public boolean equals(Object obj) {
if (obj != null && obj.getClass() == getClass()) {
ClassMetadata<?> other = (ClassMetadata<?>) obj;
return mappedClass.equals(other.mappedClass)
&& allProperties.equals(other.allProperties)
&& persistentProperties.equals(other.persistentProperties);
}
return false;
}
@Override
public int hashCode() {
int hash = 7;
hash = hash * 31 + mappedClass.hashCode();
hash = hash * 31 + allProperties.hashCode();
hash = hash * 31 + persistentProperties.hashCode();
return hash;
}
}