/** * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Copyright 2000 (C) Intalio, Inc. All Rights Reserved. * * $Id$ */ package org.exolab.castor.persist; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.SortedSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.core.util.Messages; import org.castor.jdo.util.ClassLoadingUtils; import org.castor.persist.CascadingType; import org.exolab.castor.jdo.DataObjectAccessException; import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.MapItem; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.loader.FieldDescriptorImpl; import org.exolab.castor.mapping.loader.Types; import org.exolab.castor.mapping.xml.types.FieldMappingCollectionType; /** * FieldMolder represents a field of a data object class. It is used by * ClassMolder to set and get the value from a field of a data object. * * @author <a href="mailto:yip@intalio.com">Thomas Yip</a> */ public class FieldMolder { // accepted collection types private static final String COLLECTION_TYPE_ARRAY = "array"; private static final String COLLECTION_TYPE_COLLECTION = "collection"; private static final String COLLECTION_TYPE_VECTOR = "vector"; private static final String COLLECTION_TYPE_ARRAYLIST = "arraylist"; private static final String COLLECTION_TYPE_HASHTABLE = "hashtable"; private static final String COLLECTION_TYPE_MAP = "map"; private static final String COLLECTION_TYPE_SET = "set"; private static final String COLLECTION_TYPE_HASHMAP = "hashmap"; private static final String COLLECTION_TYPE_HASHSET = "hashset"; private static final String COLLECTION_TYPE_SORTED_SET = "sortedset"; private static final String COLLECTION_TYPE_ITERATOR = "iterator"; private static final String COLLECTION_TYPE_ENUMERATON = "enumerate"; private static final String COLLECTION_TYPE_SORTED_MAP = "sortedmap"; /** * The <a href="http://jakarta.apache.org/commons/logging/">Jakarta * Commons Logging</a> instance used for all logging. */ private static Log _log = LogFactory.getFactory().getInstance(FieldMolder.class); // method prefixes private static final String METHOD_GET_PREFIX = "get"; private static final String METHOD_IS_PREFIX = "is"; private static final String METHOD_SET_PREFIX = "set"; private static final String METHOD_ADD_PREFIX = "add"; private static final String METHOD_CREATE_PREFIX = "create"; private static final String METHOD_DELETE_PREFIX = "delete"; private static final String METHOD_HAS_PREFIX = "has"; private boolean _lazy; private boolean _check; private boolean _store; private boolean _multi; private boolean _serial; private boolean _addable; private ClassMolder _enclosingClassMolder; private ClassMolder _fieldClassMolder; private Class<?> _collectionClass; private String _fieldType; private String _fieldName; private Object _default; private boolean _readonly; /** * Enlists the cascading operations defined for this field. */ private EnumSet<CascadingType> _cascading; /** Indicates whether this field has been flagged as transient, i.e. not to be considered * during any persistence operations. */ private boolean _transient; /** Specifies the {@link java.util.Comparator} instance to use with a {@link SortedSet}/SortedMap collection. */ private String _comparator; /** Collection of {@link ReflectService} instances keyed by {@link ClassLoader} instance. */ private HashMap<ClassLoader, ReflectService> _reflectServices; /** Default {@link ReflectService}. */ private ReflectService _defaultReflectService; private SQLRelationLoader _manyToManyLoader; public String toString() { return "FieldMolder for " + _enclosingClassMolder.getName() + "." + _fieldName + " of type " + getFieldTypeName(); } public String getName() { return _fieldName; } /** * Returns the java.util.Comparator instance to be used with SortedSets; null, if not specified. * @return the java.util.Comparator instance to be used with SortedSets */ public String getComparator() { return _comparator; } /* void setRelationDescriptor( RelationDescriptor rd ) throws MappingException { _loader = new SQLRelationLoader( rd, _eMold.getName() ); } public SQLRelationLoader getRelationLoader() { return _loader; }*/ public FieldPersistenceType getFieldPertsistenceType() { if (!isPersistanceCapable()) { return isSerializable() ? FieldPersistenceType.SERIALIZABLE : FieldPersistenceType.PRIMITIVE; } if (!isMulti()) { return FieldPersistenceType.PERSISTANCECAPABLE; } if (!isManyToMany()) { return FieldPersistenceType.ONE_TO_MANY; } return FieldPersistenceType.MANY_TO_MANY; } public SQLRelationLoader getRelationLoader() { return _manyToManyLoader; } public boolean isStored() { return _fieldClassMolder == null || _store; } public boolean isManyToMany() { return _manyToManyLoader != null; } public boolean isDependent() { if (_fieldClassMolder == null) { return false; } ClassMolder extendPath = _enclosingClassMolder; ClassMolder depends = _fieldClassMolder.getDepends(); while (extendPath != null) { if (extendPath == depends) { return true; } extendPath = extendPath.getExtends(); } return false; } public boolean isMulti() { return _multi; } public boolean isPersistanceCapable() { return _fieldClassMolder != null; } public boolean isSerializable() { return _serial; } public boolean isCheckDirty() { return _check; } public boolean isLazy() { return _lazy; } public boolean isAddable() { return _addable; } public boolean isTransient() { return _transient; } void setFieldClassMolder(final ClassMolder fMold) { _fieldClassMolder = fMold; } public ClassMolder getEnclosingClassMolder() { return _enclosingClassMolder; } public ClassMolder getFieldClassMolder() { return _fieldClassMolder; } public LockEngine getFieldLockEngine() { return (_fieldClassMolder == null) ? null : _fieldClassMolder.getLockEngine(); } public boolean isReadonly() { return _readonly; } public Class<?> getCollectionType() { return _collectionClass; } /** * Returns the 'cascading operations' defined for this field. * @return the 'cascading operations' defined. */ public EnumSet<CascadingType> getCascading() { return _cascading; } public Object getValue(final Object object, final ClassLoader loader) { Object internalObject = object; ReflectService rf = getContextReflectService(loader); try { // If field is accessed directly, get it's value, if not // need to call get method. It's possible to not have a // way to access the field. //if ( _handler != null ) // value = _handler.getValue( object ); //else if (rf.getField() != null) { return rf.getField().get(internalObject); } else if (rf.getGetMethod() != null) { if (rf._getSequence != null) { for (int i = 0; i < rf._getSequence.length; i++) { internalObject = rf._getSequence[i].invoke(internalObject, (Object[]) null); if (internalObject == null) { break; } } } // If field has 'has' method, false means field is null // and do not attempt to call getValue. Otherwise, if ((internalObject == null) || ((rf._hasMethod != null) && !((Boolean) rf._hasMethod.invoke( internalObject, (Object[]) null)).booleanValue())) { return null; } return rf.getGetMethod().invoke(internalObject, (Object[]) null); } else { return null; } } catch (IllegalAccessException except) { // This should never happen throw new DataObjectAccessException(Messages.format( "mapping.schemaChangeNoAccess", toString()), except); } catch (InvocationTargetException except) { // This should never happen throw new DataObjectAccessException(Messages.format("mapping.schemaChangeInvocation", toString(), except), except); } } public void addValue(final Object object, final Object value, final ClassLoader loader) { ReflectService rf = getContextReflectService(loader); if (_log.isDebugEnabled()) { _log.debug("Calling " + rf.getAddMethod().getName() + " on " + object.getClass().getName() + " with value " + value); } try { if (rf.getAddMethod() == null) { throw new DataObjectAccessException(Messages.format( "mapping.addMethodNotDefined", this.getName())); } if (value == null) { throw new NullPointerException("Adding null value is not allowed"); } rf.getAddMethod().invoke(object, new Object[] {value}); } catch (IllegalArgumentException e) { throw new DataObjectAccessException("Argument ," + value + ", cannot be added!", e); } catch (IllegalAccessException e) { throw new DataObjectAccessException("Field access error", e); } catch (InvocationTargetException e) { throw new DataObjectAccessException("Field invocation error", e); } } public void setValue(final Object object, final Object value, final ClassLoader loader) { Object internalObject = object; // If there is a convertor, apply conversion here. ReflectService rf = getContextReflectService(loader); try { //if ( _handler != null ) // _handler.setValue( object, value ); //else if (rf.getField() != null) { rf.getField().set(internalObject, (value == null) ? _default : value); } else if (rf.getSetMethod() != null) { if (rf._getSequence != null) { for (int i = 0; i < rf._getSequence.length; i++) { Object last; last = internalObject; internalObject = rf._getSequence[i].invoke(internalObject, (Object[]) null); if (internalObject == null) { // if the value is not null, we must instantiate // the object in the sequence if ((value == null) || (rf._setSequence[i] == null)) { break; } internalObject = Types.newInstance(rf._getSequence[i].getReturnType()); rf._setSequence[i].invoke(last, new Object[] {internalObject}); } } } if (internalObject != null) { if ((value == null) && (rf._deleteMethod != null)) { rf._deleteMethod.invoke(internalObject, (Object[]) null); } else { rf.getSetMethod().invoke(internalObject, new Object[] {(value == null) ? _default : value}); } } } else { throw new DataObjectAccessException( "no method to set value for field: " + getFieldTypeName() + " in class: " + _enclosingClassMolder); } // If the field has no set method, ignore it. // If this is a problem, identity it someplace else. } catch (IllegalArgumentException except) { // Graceful way of dealing with unwrapping exception if (value == null) { throw new DataObjectAccessException(Messages.format( "mapping.typeConversionNull", toString())); } throw new DataObjectAccessException(Messages.format( "mapping.typeConversion", toString(), value.getClass().getName())); } catch (IllegalAccessException except) { // This should never happen throw new DataObjectAccessException(Messages.format( "mapping.schemaChangeNoAccess", toString()), except); } catch (InvocationTargetException except) { // This should never happen throw new DataObjectAccessException(Messages.format( "mapping.schemaChangeInvocation", toString(), except.getMessage()), except); } } /** * Check if the specified value is the default value of the Field * represented by this FieldMolder. */ public boolean isDefault(final Object value) { if (_default == value) { return true; } if (_default == null) { return false; } if (_default.equals(value)) { return true; } return false; } // ====================================================== // copy from FieldHandler.java and modified // ====================================================== protected Class<?> getCollectionType(final String coll, final boolean lazy) { /* * Class type; */ for (int i = 0; i < INFO.length; i++) { if (INFO[i].getName().equals(coll)) { return INFO[i].getType(); } } return null; /* if ( "collection".equals( coll ) ) type = ArrayList.class; else if ( "vector".equals( coll ) ) type = Vector.class; else throw new MappingException( "Unsupported collection type: " + coll ); if ( lazy ) return null; else return type;*/ } public static class CollectionInfo { private String _name; private Class<?> _type; public CollectionInfo(final String name, final Class<?> type) { _name = name; _type = type; } public String getName() { return _name; } public Class<?> getType() { return _type; } } private static final CollectionInfo[] INFO = {new CollectionInfo(COLLECTION_TYPE_COLLECTION, java.util.Collection.class), new CollectionInfo(COLLECTION_TYPE_VECTOR, java.util.Vector.class), new CollectionInfo(COLLECTION_TYPE_ARRAYLIST, java.util.ArrayList.class), new CollectionInfo(COLLECTION_TYPE_HASHTABLE, java.util.Hashtable.class), new CollectionInfo(COLLECTION_TYPE_HASHMAP, java.util.HashMap.class), new CollectionInfo(COLLECTION_TYPE_SET, java.util.Set.class), new CollectionInfo(COLLECTION_TYPE_HASHSET, java.util.HashSet.class), new CollectionInfo(COLLECTION_TYPE_MAP, java.util.Map.class), new CollectionInfo(COLLECTION_TYPE_ARRAY, Object[].class), new CollectionInfo(COLLECTION_TYPE_SORTED_SET, java.util.SortedSet.class), new CollectionInfo(COLLECTION_TYPE_ITERATOR, java.util.Iterator.class), new CollectionInfo(COLLECTION_TYPE_ENUMERATON, java.util.Enumeration.class), new CollectionInfo(COLLECTION_TYPE_SORTED_MAP, java.util.SortedMap.class)}; //( array | vector | hashtable | collection | set | map ) /** * Creates a single field descriptor. The field mapping is used to * create a new stock {@link FieldMolder}. Implementations may * extend this class to create a more suitable descriptor. * * @param eMold The ClassMolder to which the field belongs * @param fieldMapping The field mapping information * @throws MappingException The field or its accessor methods are not * found, not accessible, not of the specified type, etc */ public FieldMolder(final DatingService ds, final ClassMolder eMold, final FieldDescriptor fieldDescriptor, final SQLRelationLoader loader) throws MappingException { this(ds, eMold, fieldDescriptor); _manyToManyLoader = loader; } public FieldMolder(final DatingService datingService, final ClassMolder enclosingClassMolder, final FieldDescriptor fieldDescriptor) throws MappingException { String fieldName = fieldDescriptor.getFieldName(); String fieldType = fieldDescriptor.getFieldType().getName(); try { // create the reflection service with the ClassLoader hold in the // SatingService object as default _defaultReflectService = new ReflectService(); _reflectServices = new HashMap<ClassLoader, ReflectService>(); // Set enclosing ClassMolder _enclosingClassMolder = enclosingClassMolder; if ("java.io.Serializable".equals(fieldType)) { _serial = true; } // check whether complete field is declared transient _transient = fieldDescriptor.isTransient(); dealWithSqlMapping(fieldDescriptor); // check if comparator is specified, and if so, use it String comparator = ((FieldDescriptorImpl) fieldDescriptor).getComparator(); if (comparator != null) { _comparator = comparator; } establishCollectionDefinition(datingService, ((FieldDescriptorImpl) fieldDescriptor).getCollection(), fieldType); // Set field name, if it is null, we try to discover it with // return type of set/get method. setFieldTypeName(fieldType); Class enclosingClass; try { enclosingClass = datingService.resolve(enclosingClassMolder.getName()); } catch (ClassNotFoundException e) { throw new MappingException("mapping.classNotFound", enclosingClassMolder.getName()); } // ssa, multi classloader feature // set the default classloader to the hash table // ssa, FIXME : Shoudln't we have a ref to the Classloader used // instead of asking it to the newly created class ? _defaultReflectService._loader = enclosingClass.getClassLoader(); if (null != _defaultReflectService._loader) { _reflectServices.put(_defaultReflectService._loader, this._defaultReflectService); } Class declaredClass = null; if (fieldType != null) { try { declaredClass = Types.typeFromName(enclosingClass.getClassLoader(), fieldType); _defaultReflectService.setFieldType(declaredClass); } catch (ClassNotFoundException cnfe) { throw new MappingException("mapping.classNotFound", declaredClass); } } if (((FieldDescriptorImpl) fieldDescriptor).isDirect()) { establishDirectFieldAccess(fieldName, enclosingClass); } else { String getMethod = ((FieldDescriptorImpl) fieldDescriptor).getGetMethod(); String setMethod = ((FieldDescriptorImpl) fieldDescriptor).getSetMethod(); if ((getMethod == null) && (setMethod == null)) { // Container object, map field to fields of the container int point; ArrayList<Method> getSeq = new ArrayList<Method>(); ArrayList<Method> setSeq = new ArrayList<Method>(); String name = fieldName; Class<?> last; Method method = null; String methodName = null; try { while (true) { point = name.indexOf('.'); if (point < 0) { break; } last = enclosingClass; if (fieldType.compareTo("boolean") == 0 || fieldType.compareTo("java.lang.Boolean") == 0) { try { methodName = METHOD_IS_PREFIX + capitalize(name.substring(0, point)); method = enclosingClass.getMethod(methodName, (Class[]) null); } catch (NoSuchMethodException nsme) { if (_log.isDebugEnabled()) { _log.debug (Messages.format("mapping.accessorNotFound", methodName, "boolean", getName())); } } } if (method == null) { methodName = METHOD_GET_PREFIX + capitalize(name.substring(0, point)); method = enclosingClass.getMethod(methodName, (Class[]) null); } name = name.substring(point + 1); // Make sure method is not abstract/static // (note: Class.getMethod() returns only public methods). if (((method.getModifiers() & Modifier.ABSTRACT) != 0) || ((method.getModifiers() & Modifier.STATIC) != 0)) { throw new MappingException("mapping.accessorNotAccessible", methodName, enclosingClass.getName()); } getSeq.add(method); enclosingClass = method.getReturnType(); // setter; Note: javaClass already changed, use "last" if (fieldType.compareTo("boolean") == 0 || fieldType.compareTo("java.lang.Boolean") == 0) { methodName = METHOD_SET_PREFIX + methodName.substring(2); } else { methodName = METHOD_SET_PREFIX + methodName.substring(3); } try { method = last.getMethod(methodName, new Class[] {enclosingClass}); if (((method.getModifiers() & Modifier.ABSTRACT) != 0) || ((method.getModifiers() & Modifier.STATIC) != 0)) { method = null; } } catch (Exception except) { method = null; } setSeq.add(method); method = null; } } catch (Exception ex) { throw new MappingException(Messages.format ("mapping.accessorNotFound", methodName, null, enclosingClass.getName()), ex); } if (getSeq.size() > 0) { _defaultReflectService._getSequence = getSeq.toArray(new Method[getSeq.size()]); _defaultReflectService._setSequence = setSeq.toArray(new Method[setSeq.size()]); } Class methodClass = (_collectionClass != null) ? _collectionClass : declaredClass; _defaultReflectService.setGetMethod(null); // if field is of type boolean, check whether is<Field>() is defined. if (fieldType != null && (fieldType.compareTo("boolean") == 0 || fieldType.compareTo("java.lang.Boolean") == 0)) { _defaultReflectService.setGetMethod(findAccessor( enclosingClass, METHOD_IS_PREFIX + capitalize(name), methodClass, true)); } if (_defaultReflectService.getGetMethod() == null) { _defaultReflectService.setGetMethod(findAccessor( enclosingClass, METHOD_GET_PREFIX + capitalize(name), methodClass, true)); } Method getMethodTemp = _defaultReflectService.getGetMethod(); if (getMethodTemp == null) { if (fieldType.compareTo("boolean") == 0 || fieldType.compareTo("java.lang.Boolean") == 0) { throw new MappingException("mapping.accessorNotFound", METHOD_GET_PREFIX + "/" + METHOD_IS_PREFIX + capitalize(name), fieldType, enclosingClassMolder.getName()); } throw new MappingException("mapping.accessorNotFound", METHOD_GET_PREFIX + capitalize(name), fieldType, enclosingClassMolder.getName()); } // update fClass, because we can't tell between primitive // and primitive wrapper from the mapping if (_collectionClass == null) { _defaultReflectService.setFieldType(_defaultReflectService.getGetMethod().getReturnType()); } _defaultReflectService.setSetMethod(findAccessor( enclosingClass, METHOD_SET_PREFIX + capitalize(name), methodClass, false)); if (_defaultReflectService.getSetMethod() == null) { _defaultReflectService.setAddMethod(findAccessor( enclosingClass, METHOD_ADD_PREFIX + capitalize(name), declaredClass, false)); // look again, but this time without a trailing 's' if ((_defaultReflectService.getAddMethod() == null) && (name.endsWith("s"))) { _defaultReflectService.setAddMethod(findAccessor( enclosingClass, METHOD_ADD_PREFIX + capitalize(name).substring(0, name.length() - 1), declaredClass, false)); } // if add<FieldName>() has been found, set _addable to true if (_defaultReflectService.getAddMethod() != null) { _addable = true; } } if ((_defaultReflectService.getSetMethod() == null) && (_defaultReflectService.getAddMethod() == null)) { throw new MappingException("mapping.accessorNotFound", METHOD_SET_PREFIX + "/" + METHOD_ADD_PREFIX + capitalize(name), declaredClass, enclosingClass.getName()); } } else { // there's a get method and/or set method specified, but we don't know whether // both are specified // NOTE: a get method has to be specified once a set method has been provided // Bean type object, map field to get<Method>/set<Method> Class<?> fieldClassType = _defaultReflectService.getFieldType(); establishGetMethod(enclosingClass, getMethod, fieldClassType); establishSetAndAddMethod(((FieldDescriptorImpl) fieldDescriptor).isLazy(), enclosingClass, declaredClass, setMethod, fieldClassType); } } establishCreateMethod(((FieldDescriptorImpl) fieldDescriptor).getCreateMethod(), fieldName, enclosingClass); establishHasAndDeleteMethods(fieldName, enclosingClass); if ((_defaultReflectService.getField() == null) && (_defaultReflectService.getSetMethod() == null) && (_defaultReflectService.getGetMethod() == null)) { throw new MappingException("_field or _setMethod can't be created"); } datingService.pairFieldClass(this, getFieldTypeName()); } catch (NullPointerException e) { _log.fatal("Caught unexpected NullPointerException: ", e); throw new MappingException("Unexpected Null pointer!\n" + e); } _fieldName = fieldName; // If the field is of a primitive type we use the default value _default = Types.getDefault(_defaultReflectService.getFieldType()); // make the default to null for wrappers of primitives if (!_defaultReflectService.getFieldType().isPrimitive()) { _default = null; } } /** * @param datingService * @param fieldMapping * @param fieldType * @throws MappingException */ private void establishCollectionDefinition( final DatingService datingService, final FieldMappingCollectionType collectionType, String fieldType) throws MappingException { if (collectionType != null) { _multi = true; // simple arrays support if (COLLECTION_TYPE_ARRAY.equals(collectionType.toString())) { String arrayClassName = "[L" + fieldType + ";"; try { _collectionClass = datingService.resolve(arrayClassName); } catch (ClassNotFoundException e) { throw new MappingException("mapping.classNotFound", arrayClassName); } } else { _collectionClass = getCollectionType(collectionType.toString(), _lazy); if (_collectionClass != SortedSet.class && _comparator != null) { throw new MappingException( Messages.message("mapping.wrong.use.of.comparator")); } } _store = false; } } /** * If direct field access is configured, set the (directly accessed) field on the {@link FieldHandler}. * @param fieldName The name of the field. * @param enclosingClass The class type of the enclosing class. * @throws MappingException If no such field is present on the class in question. */ private void establishDirectFieldAccess(String fieldName, Class<?> enclosingClass) throws MappingException { Class<?> fieldClass = (_collectionClass != null) ? _collectionClass : null; _defaultReflectService.setField(findField(enclosingClass, fieldName, fieldClass)); if (_defaultReflectService.getField() == null) { throw new MappingException(Messages.format( "mapping.fieldNotAccessible", fieldName, enclosingClass.getName())); } _defaultReflectService.setFieldType(_defaultReflectService.getField().getType()); int modifiers = _defaultReflectService.getField().getModifiers(); if ((modifiers != Modifier.PUBLIC) && (modifiers != (Modifier.PUBLIC | Modifier.VOLATILE))) { throw new MappingException(Messages.format( "mapping.fieldNotAccessible", _defaultReflectService.getField().getName(), _defaultReflectService.getField().getDeclaringClass().getName())); } } /** * Add a set and/or add method to the {@link FieldHandler} if given. * @param isLazy Indicates whether the current field is lazy. * @param enclosingClass The enclosing class type. * @param declaredClass The declaring class type. * @param setMethod The name of the set method (if specified). * @param fieldClassType The field class type. * @throws MappingException If the given method is not present on the enclosing class. */ private void establishSetAndAddMethod(final boolean isLazy, Class<?> enclosingClass, Class<?> declaredClass, String setMethod, Class<?> fieldClassType) throws MappingException { if (setMethod != null) { if (_collectionClass != null) { _defaultReflectService.setSetMethod(findAccessor( enclosingClass, setMethod, _collectionClass, false)); // find addXXX method only if lazy loading is turned off if ((_defaultReflectService.getSetMethod() == null) && !isLazy) { _defaultReflectService.setAddMethod(findAccessor( enclosingClass, setMethod, declaredClass, false)); if (_defaultReflectService.getAddMethod() != null) { _addable = true; } } } else { // find setXXX method _defaultReflectService.setSetMethod(findAccessor( enclosingClass, setMethod, fieldClassType, false)); } if ((_defaultReflectService.getSetMethod() == null) && (_defaultReflectService.getAddMethod() == null)) { throw new MappingException("mapping.accessorNotFound", setMethod, fieldClassType, enclosingClass.getName()); } if (_defaultReflectService.getFieldType() == null) { _defaultReflectService.setFieldType(_defaultReflectService.getSetMethod().getParameterTypes()[0]); } } else { throw new MappingException("mapping.setMethodMappingNotFound", (_collectionClass != null) ? _collectionClass : fieldClassType, enclosingClass.getName()); } } /** * Add a get method to the {@link FieldHandler} if specified. * @param enclosingClass The enclosing Class type. * @param getMethod The Name of the get method (null possible, if not specified). * @param fieldClassType The Class type of the field. * @throws MappingException If the method defined is not present on the given enclosing class. */ private void establishGetMethod(Class<?> enclosingClass, String getMethod, Class<?> fieldClassType) throws MappingException { if (getMethod != null) { if (_collectionClass != null) { _defaultReflectService.setGetMethod(findAccessor( enclosingClass, getMethod, _collectionClass, true)); } else { _defaultReflectService.setGetMethod(findAccessor( enclosingClass, getMethod, fieldClassType, true)); } if (_defaultReflectService.getGetMethod() == null) { throw new MappingException("mapping.accessorNotFound", getMethod, fieldClassType, enclosingClass.getName()); } // set/reset the fClass to actual field class if (_collectionClass == null) { _defaultReflectService.setFieldType(_defaultReflectService.getGetMethod().getReturnType()); } } else { throw new MappingException("mapping.getMethodMappingNotFound", (_collectionClass != null) ? _collectionClass : fieldClassType, enclosingClass.getName()); } } /** * Adds has and delete methods to the {@link FieldHandler} if present. * @param fieldName Name of the field. * @param javaClass Class type of the field. */ private void establishHasAndDeleteMethods(String fieldName, Class<?> javaClass) { if (fieldName != null) { Method hasMethod = null; Method deleteMethod = null; try { hasMethod = javaClass.getMethod(METHOD_HAS_PREFIX + capitalize(fieldName), (Class[]) null); if (((hasMethod.getModifiers() & Modifier.PUBLIC) == 0) || ((hasMethod.getModifiers() & Modifier.STATIC) != 0)) { hasMethod = null; } try { if (((hasMethod.getModifiers() & Modifier.PUBLIC) == 0) || ((hasMethod.getModifiers() & Modifier.STATIC) != 0)) { deleteMethod = null; } deleteMethod = javaClass.getMethod(METHOD_DELETE_PREFIX + capitalize(fieldName), (Class[]) null); } catch (Exception except) { // no explicit exception handling } _defaultReflectService._hasMethod = hasMethod; _defaultReflectService._deleteMethod = deleteMethod; } catch (Exception except) { // no explicit exception handling } } } /** * Adds a create method to the {@link FieldHandler} if specified. * @param createMethod Name of the method to create object instances. * @param fieldName Name of the field. * @param javaClass Class type. * @throws MappingException If the specified method is not present on the given class type. */ private void establishCreateMethod(String createMethod, String fieldName, Class<?> javaClass) throws MappingException { // If there is a create method, add it to the field handler // Note: create method is used for enclosing object of this field to // determine // what exact instance to be created. if (createMethod != null) { try { _defaultReflectService.setCreateMethod(javaClass.getMethod( createMethod, (Class[]) null)); } catch (Exception except) { // No such/access to method throw new MappingException("mapping.createMethodNotFound", createMethod, javaClass.getName()); } } else if ((fieldName != null) && !Types.isSimpleType(_defaultReflectService.getFieldType())) { try { Method method; method = javaClass.getMethod(METHOD_CREATE_PREFIX + capitalize(fieldName), (Class[]) null); _defaultReflectService.setCreateMethod(method); } catch (Exception except) { // no explicit exception handling } } } private void dealWithSqlMapping(FieldDescriptor fieldDescriptor) throws MappingException { if (fieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) { FieldDescriptorJDONature nature = new FieldDescriptorJDONature(fieldDescriptor); if (nature.isDirtyCheck()) { _check = true; } if (nature.getManyTable() != null) { _store = false; } else if (nature.getSQLName() == null || nature.getSQLName().length == 0) { _store = false; } else { _store = true; } _readonly = nature.isReadonly(); _cascading = EnumSet.noneOf(CascadingType.class); // TODO: in the schema, cascading is still simply a string. // when this is finally made into a proper list (enumeration and all) // this will probably have to be changed // TODO: also, we should probably use constants // NOTE: we assume here that the types are delimited by whitespace if (nature.getCascading() != null) { String[] temp = nature.getCascading().toLowerCase().trim().split("\\s+"); List<String> cascadingTypes = java.util.Arrays.asList(temp); if (cascadingTypes.contains("all")) { _cascading = EnumSet.allOf(CascadingType.class); } else { if (cascadingTypes.contains("create")) { _cascading.add(CascadingType.CREATE); } if (cascadingTypes.contains("delete")) { _cascading.add(CascadingType.DELETE); } if (cascadingTypes.contains("update")) { _cascading.add(CascadingType.UPDATE); } } } boolean isSQLTransient = nature.isTransient(); if (_transient && !isSQLTransient) { throw new MappingException (Messages.message("persist.transient.conflict")); } _transient = isSQLTransient; } _lazy = ((FieldDescriptorImpl) fieldDescriptor).isLazy(); } /** * Returns the named field. Uses reflection to return the named * field and check the field type, if specified. * * @param javaClass The class to which the field belongs * @param fieldName The name of the field * @param fieldType The type of the field if known, or null * @return The field, null if not found * @throws MappingException The field is not accessible or is not of the * specified type */ private Field findField(final Class<?> javaClass, final String fieldName, final Class<?> fieldType) throws MappingException { Class<?> internalFieldType = fieldType; Field field; try { // Look up the field based on its name, make sure it's only modifier // is public. If a type was specified, match the field type. field = javaClass.getField(fieldName); if ((field.getModifiers() != Modifier.PUBLIC) && (field.getModifiers() != (Modifier.PUBLIC | Modifier.VOLATILE))) { throw new MappingException("mapping.fieldNotAccessible", fieldName, javaClass.getName()); } if (internalFieldType == null) { internalFieldType = Types.typeFromPrimitive(field.getType()); } else if (Types.typeFromPrimitive(internalFieldType) != Types.typeFromPrimitive(field.getType())) { throw new MappingException("mapping.fieldTypeMismatch", field, internalFieldType.getName()); } return field; } catch (NoSuchFieldException except) { // no explicit exception handling } catch (SecurityException except) { // no explicit exception handling } return null; } /** * Returns the named accessor. Uses reflection to return the named accessor and * check the return value or parameter type, if specified. * * @param javaClass The class to which the field belongs. * @param methodName The name of the accessor method. * @param fieldType The type of the field if known, or null. * @param getMethod True if get method, false if set method. * @return The method, null if not found. * @throws MappingException The method is not accessible or is not of the * specified type. */ public static final Method findAccessor(final Class<?> javaClass, final String methodName, final Class<?> fieldType, final boolean getMethod) throws MappingException { Class<?> internalFieldType = fieldType; try { Method method = null; if (getMethod) { // Get method: look for the named method or prepend get to the method // name. Look up the field and potentially check the return type. method = javaClass.getMethod(methodName, new Class[0]); // The MapItem is used to handle the contents of maps. Since the MapItem // has to use Object for its methods we cannot (but also don't have to) // check for correct types. if (javaClass == MapItem.class) { if (methodName.equals("getKey")) { return method; } if (methodName.equals("getValue")) { return method; } } if (internalFieldType == null) { internalFieldType = Types.typeFromPrimitive(method.getReturnType()); } else { internalFieldType = Types.typeFromPrimitive(internalFieldType); Class<?> returnType = Types.typeFromPrimitive(method.getReturnType()); //-- First check against whether the declared type is //-- an interface or abstract class. We also check //-- type as Serializable for CMP 1.1 compatibility. if (internalFieldType.isInterface() || ((internalFieldType.getModifiers() & Modifier.ABSTRACT) != 0) || (internalFieldType == java.io.Serializable.class)) { if (!internalFieldType.isAssignableFrom(returnType)) { throw new MappingException( "mapping.accessorReturnTypeMismatch", method, internalFieldType.getName()); } } else { if (!returnType.isAssignableFrom(internalFieldType)) { throw new MappingException( "mapping.accessorReturnTypeMismatch", method, internalFieldType.getName()); } } } } else { // Set method: look for the named method or prepend set to the method // name. If the field type is know, look up a suitable method. If the // field type is unknown, lookup the first method with that name and // one parameter. Class<?> fieldTypePrimitive = null; if (internalFieldType != null) { fieldTypePrimitive = Types.typeFromPrimitive(internalFieldType); // first check for setter with reference type (e.g. setXxx(Integer)) try { method = javaClass.getMethod(methodName, new Class[] {fieldTypePrimitive}); } catch (Exception ex) { // if setter for reference type could not be found // try to find one for primitive type (e.g. setXxx(int)) try { method = javaClass.getMethod( methodName, new Class[] {internalFieldType}); } catch (Exception ex2) { // LOG.warn("Unexpected exception", ex2); } } } if (method == null) { Method[] methods = javaClass.getMethods(); for (int i = 0; i < methods.length; ++i) { if (methods[i].getName().equals(methodName)) { Class[] paramTypes = methods[i].getParameterTypes(); if (paramTypes.length != 1) { continue; } Class<?> paramType = Types.typeFromPrimitive(paramTypes[0]); if (internalFieldType == null) { method = methods[i]; break; } else if (paramType.isAssignableFrom(fieldTypePrimitive)) { method = methods[i]; break; } else if (internalFieldType.isInterface() || isAbstract(internalFieldType)) { if (fieldTypePrimitive.isAssignableFrom(paramType)) { method = methods[i]; break; } } } } if (method == null) { return null; } } } // Make sure method is public and not static. // (note: Class.getMethod() returns only public methods). if ((method.getModifiers() & Modifier.STATIC) != 0) { throw new MappingException( "mapping.accessorNotAccessible", methodName, javaClass.getName()); } return method; } catch (MappingException ex) { throw ex; } catch (Exception ex) { return null; } } private static boolean isAbstract(final Class<?> cls) { return ((cls.getModifiers() & Modifier.ABSTRACT) != 0); } private String capitalize(final String name) { char first; first = name.charAt(0); if (Character.isUpperCase(first)) { return name; } return Character.toUpperCase(first) + name.substring(1); } /** * Get the {@link ReflectService} used given a {@link ClassLoader} instance. * * @param loader the current {@link ClassLoader} instance. * @return the {@link ReflectService} instance for the given {@link ClassLoader}. If doess't yet exist * for the given {@link ClassLoader}, then it creates one prior to returning it. */ private ReflectService getContextReflectService(final ClassLoader loader) { if ((null == loader) || (this._defaultReflectService._loader == loader)) { return this._defaultReflectService; } ReflectService resultReflectService = _reflectServices.get(loader); if (null == resultReflectService) { // create a new ReflectService and store it in the hashtable resultReflectService = new ReflectService(_defaultReflectService, loader); } return resultReflectService; } private void setFieldTypeName(String fieldType) { _fieldType = fieldType; } private String getFieldTypeName() { return _fieldType; } /** * Provides all the necessary instances of <code>Method</code>, <code>Class</code> * and <code>Field</code> for a given <code>ClassLoader</code> instance. * */ private class ReflectService { /** * Default constructor. */ public ReflectService () { } /** * Constructs a ReflectService object based on the instance provided * and uses the <code>ClassLoader</code> to build Reflection fields. * * @param refSrv the ReflectService that serve as a based for the new instance * @loader the new ClassLoader */ public ReflectService(final ReflectService refSrv, final ClassLoader loader) { this._loader = loader; this.setFieldType(cloneClass(refSrv.getFieldType())); this.setField(cloneField(refSrv.getField())); this._getSequence = cloneMethods(refSrv._getSequence); this._setSequence = cloneMethods(refSrv._setSequence); this.setGetMethod(cloneMethod(refSrv.getGetMethod())); this.setAddMethod(cloneMethod(refSrv.getAddMethod())); this.setSetMethod(cloneMethod(refSrv.getSetMethod())); this._hasMethod = cloneMethod(refSrv._hasMethod); this._deleteMethod = cloneMethod(refSrv._deleteMethod); this.setCreateMethod(cloneMethod(refSrv.getCreateMethod())); } private ClassLoader _loader; private Class<?> fieldType; private Field _field; private Method[] _getSequence; private Method[] _setSequence; private Method _getMethod; private Method _setMethod; private Method _addMethod; private Method _hasMethod; private Method _deleteMethod; private Method _createMethod; /** * constructs a Field instance with the current ClassLoader. */ private Field cloneField(final Field originalField) { if (null == originalField) { return null; } Field resultField = null; try { resultField = originalField.getDeclaringClass().getField(originalField.getName()); } catch (NoSuchFieldException e) { // ssa, FIXME shoudl never happen e.printStackTrace(); } return resultField; } /** * constructs a Method instance with the current ClassLoader. */ private Method cloneMethod(final Method originalMethod) { if (null == originalMethod) { return null; } Method resultMethod = null; try { Class<?> newCls = loadClass(originalMethod.getDeclaringClass().getName()); String methodName = originalMethod.getName(); Class[] methodParams = originalMethod.getParameterTypes(); for (int i = 0; i < methodParams.length; i++) { if (!methodParams[i].isPrimitive()) { methodParams[i] = loadClass(methodParams[i].getName()); } } resultMethod = newCls.getMethod(methodName, methodParams); } catch (NoSuchMethodException e) { // ssa, FIXME shoudl never happen e.printStackTrace(); } return resultMethod; } /** * constructs an Array of Method instances with the current ClassLoader. */ private Method[] cloneMethods(final Method[] originalMethods) { if (null == originalMethods) { return null; } Method [] resultMethods = new Method[originalMethods.length]; for (int i = 0; i < originalMethods.length; i++) { resultMethods[i] = cloneMethod(originalMethods[i]); } return resultMethods; } /** * constructs a <code>Class</code> instance with the current ClassLoader. */ private Class<?> cloneClass(final Class<?> originalClass) { if (null == originalClass) { return null; } if (originalClass.isPrimitive()) { return originalClass; } return loadClass(originalClass.getName()); } /** * Helper method to load the class given its full qualified name. */ private Class<?> loadClass(final String name) { Class<?> resultClass = null; try { resultClass = ClassLoadingUtils.loadClass(_loader, name); } catch (ClassNotFoundException e) { // ssa, FIXME : should never happen e.printStackTrace(); } return resultClass; } private void setCreateMethod(Method createMethod) { _createMethod = createMethod; } private Method getCreateMethod() { return _createMethod; } private void setField(Field field) { _field = field; } private Field getField() { return _field; } private void setFieldType(Class<?> fieldType) { this.fieldType = fieldType; } private Class<?> getFieldType() { return fieldType; } private void setGetMethod(Method getMethod) { _getMethod = getMethod; } private Method getGetMethod() { return _getMethod; } private void setSetMethod(Method setMethod) { _setMethod = setMethod; } private Method getSetMethod() { return _setMethod; } private void setAddMethod(Method addMethod) { _addMethod = addMethod; } private Method getAddMethod() { return _addMethod; } } }