| /** |
| * 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; |
| } |
| } |