/** * 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 1999 (C) Intalio, Inc. All Rights Reserved. * * $Id$ */ package org.exolab.castor.jdo.engine; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.cache.Cache; import org.castor.cache.simple.CountLimited; import org.castor.cache.simple.TimeLimited; import org.castor.core.util.AbstractProperties; import org.castor.core.util.Messages; import org.castor.cpa.CPAProperties; import org.castor.cpa.persistence.convertor.AbstractSimpleTypeConvertor; import org.castor.cpa.persistence.convertor.TypeConvertorRegistry; import org.castor.cpa.util.classresolution.command.ClassResolutionByFile; import org.castor.cpa.util.classresolution.nature.ClassLoaderNature; import org.castor.jdo.engine.SQLTypeInfos; import org.castor.mapping.BindingType; import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature; import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature; import org.exolab.castor.mapping.AccessMode; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.CollectionHandler; import org.exolab.castor.mapping.ExtendedFieldHandler; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.GeneralizedFieldHandler; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.TypeConvertor; import org.exolab.castor.mapping.loader.FieldDescriptorImpl; import org.exolab.castor.mapping.loader.AbstractMappingLoader; import org.exolab.castor.mapping.loader.ClassDescriptorImpl; import org.exolab.castor.mapping.loader.CollectionHandlers; import org.exolab.castor.mapping.loader.FieldHandlerFriend; import org.exolab.castor.mapping.loader.FieldHandlerImpl; import org.exolab.castor.mapping.loader.TypeInfo; import org.exolab.castor.mapping.loader.Types; import org.exolab.castor.mapping.xml.CacheTypeMapping; import org.exolab.castor.mapping.xml.ClassMapping; import org.exolab.castor.mapping.xml.FieldMapping; import org.exolab.castor.mapping.xml.KeyGeneratorDef; import org.exolab.castor.mapping.xml.MappingRoot; import org.exolab.castor.mapping.xml.NamedNativeQuery; import org.exolab.castor.mapping.xml.NamedQuery; import org.exolab.castor.mapping.xml.Param; import org.exolab.castor.mapping.xml.Sql; import org.exolab.castor.mapping.xml.types.SqlDirtyType; import org.exolab.castor.persist.spi.PersistenceFactory; /** * A JDO implementation of mapping helper. Creates JDO class descriptors * from the mapping file. * * @author <a href="mailto:arkin AT intalio DOT com">Assaf Arkin</a> * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a> * @version $Revision$ $Date: 2006-04-13 07:37:49 -0600 (Thu, 13 Apr 2006) $ */ public final class JDOMappingLoader extends AbstractMappingLoader { /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta Commons * Logging </a> instance used for all logging. */ private static final Log LOG = LogFactory.getLog(JDOMappingLoader.class); /** Separators between type name and parameter, e.g. "char[01]". */ private static final char LEFT_PARAM_SEPARATOR = '['; /** Separators after parameter, e.g. "char[01]". */ private static final char RIGHT_PARAM_SEPARATOR = ']'; /** * Extracts parameter for type convertor from the SQL type definition of the * form "SQL_TYPE_NAME[PARAMETER]". If the type is not parameterized, returns * null. * * @param sqlTypeDef SQL type definition (e.g. char[01]). * @return Parameter (e.g. "01") or null if not parameterized. */ public static String definition2param(final String sqlTypeDef) { int left = sqlTypeDef.indexOf(LEFT_PARAM_SEPARATOR); int right = sqlTypeDef.indexOf(RIGHT_PARAM_SEPARATOR); if (right < 0) { right = sqlTypeDef.length(); } if (left < 0) { return null; } return sqlTypeDef.substring(left + 1, right); } /** * Extracts SQL type name from the the SQL type definition of the form * "SQL_TYPE_NAME[PARAMETER]". * * @param sqlTypeDef SQL type definition (e.g. char[01]). * @return SQL type name (e.g. "char"). */ public static String definition2type(final String sqlTypeDef) { int sep = sqlTypeDef.indexOf(LEFT_PARAM_SEPARATOR); if (sep < 0) { return sqlTypeDef; } return sqlTypeDef.substring(0, sep); } private final TypeConvertorRegistry _typeConvertorRegistry; /** * Map of key generator descriptors associated by their name. */ private final Map<String, KeyGeneratorDescriptor> _keyGeneratorDescriptors = new HashMap<String, KeyGeneratorDescriptor>(); /** Set of names of all named queries to identify duplicate names. */ private final Set<String> _queryNames = new HashSet<String>(); /** * The JDO {@link PersistenceFactory} is used for adjusting SQL type for * the given database. */ private PersistenceFactory _factory; /** * Creates an instance of {@link JDOMappingLoader}, providing a * {@link ClassLoader} instance. * @param loader A Custom {@link ClassLoader} instance. */ public JDOMappingLoader(final ClassLoader loader) { super(loader); AbstractProperties properties = CPAProperties.getInstance(); _typeConvertorRegistry = new TypeConvertorRegistry(properties); } /** * {@inheritDoc} */ public BindingType getBindingType() { return BindingType.JDO; } /** * {@inheritDoc} */ public void loadMapping(final MappingRoot mapping, final Object param) throws MappingException { if (loadMapping()) { _factory = (PersistenceFactory) param; createKeyGenDescriptors(mapping); createClassDescriptors(mapping); } } /** * Load key generator definitions, check for duplicate definitions and convert them * to key generator descriptors. * * @param mapping Mapping to load key generator defintions from. * @throws MappingException If mapping contains more then one key generator * definition with same name. */ private void createKeyGenDescriptors(final MappingRoot mapping) throws MappingException { Enumeration enumeration = mapping.enumerateKeyGeneratorDef(); while (enumeration.hasMoreElements()) { KeyGeneratorDef def = (KeyGeneratorDef) enumeration.nextElement(); // resolve name of the key generator definition; if there's an alias // defined, use it; if not, fall back to the name attribute (this // is to ensure that for one key generator type (e.g. HIGH-LOW), it is // possible to define more than one instance with individual // configuration(s). String name = def.getAlias(); if (name == null) { name = def.getName(); } KeyGeneratorDescriptor desc = _keyGeneratorDescriptors.get(name); if (desc != null) { throw new MappingException(Messages.format("mapping.dupKeyGen", name)); } Properties params = new Properties(); Enumeration enumerateParam = def.enumerateParam(); while (enumerateParam.hasMoreElements()) { Param par = (Param) enumerateParam.nextElement(); params.put(par.getName(), par.getValue()); } desc = new KeyGeneratorDescriptor( name, def.getName(), params); _keyGeneratorDescriptors.put(name, desc); } } protected ClassDescriptor createClassDescriptor(final ClassMapping classMapping) throws MappingException { // If there is no SQL information for class, ignore it. if ((classMapping.getMapTo() == null) || (classMapping.getMapTo().getTable() == null)) { LOG.info(Messages.format("mapping.ignoringMapping", classMapping.getName())); return null; } // Create the class descriptor, and register the JDO nature with it. ClassDescriptorImpl clsDesc = new ClassDescriptorImpl(); clsDesc.addNature(ClassDescriptorJDONature.class.getName()); ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(clsDesc); // Obtain the Java class. Class<?> javaClass = resolveType(classMapping.getName()); if (!Types.isConstructable(javaClass, true)) { throw new MappingException( "mapping.classNotConstructable", javaClass.getName()); } clsDesc.setJavaClass(javaClass); // If this class extends another class, we need to obtain the extended // class and make sure this class indeed extends it. ClassDescriptor extDesc = getExtended(classMapping, javaClass); if (extDesc != null) { if (!(extDesc.hasNature(ClassDescriptorJDONature.class.getName()))) { throw new IllegalArgumentException( "Extended class does not have a JDO descriptor"); } new ClassDescriptorJDONature(extDesc).addExtended(clsDesc); } clsDesc.setExtends(extDesc); // If this class depends on another class, obtain the class it depends upon. clsDesc.setDepends(getDepended(classMapping, javaClass)); // Create all field descriptors. FieldDescriptorImpl[] allFields = createFieldDescriptors(classMapping, javaClass); // Make sure there are no two fields with the same name. checkFieldNameDuplicates(allFields, javaClass); // Set class descriptor containing the field for (int i = 0; i < allFields.length; i++) { allFields[i].setContainingClassDescriptor(clsDesc); } // Identify identity and normal fields. Note that order must be preserved. List<FieldDescriptor> fieldList = new ArrayList<FieldDescriptor>(allFields.length); List<FieldDescriptor> idList = new ArrayList<FieldDescriptor>(); if (extDesc == null) { // Sort fields into 2 lists based on identity definition of field. for (int i = 0; i < allFields.length; i++) { if (!allFields[i].isIdentity()) { fieldList.add(allFields[i]); } else { idList.add(allFields[i]); } } if (idList.size() == 0) { // Found no identities based on identity definition of field. // Try to find identities based on identity definition on class. String[] idNames = classMapping.getIdentity(); if ((idNames == null) || (idNames.length == 0)) { // There are also no identity definitions on class. throw new MappingException("mapping.noIdentity", javaClass.getName()); } FieldDescriptor identity; for (int i = 0; i < idNames.length; i++) { identity = findIdentityByName(fieldList, idNames[i], javaClass); if (identity != null) { idList.add(identity); } else { throw new MappingException("mapping.identityMissing", idNames[i], javaClass.getName()); } } } } else { // Add all fields of extending class to field list. for (int i = 0; i < allFields.length; i++) { fieldList.add(allFields[i]); } // Add all identities of extended class to identity list. FieldDescriptor[] extIds = ((ClassDescriptorImpl) extDesc).getIdentities(); for (int i = 0; i < extIds.length; i++) { idList.add(extIds[i]); } // Search redefined identities in extending class. FieldDescriptor identity; for (int i = 0; i < idList.size(); i++) { String idName = (idList.get(i)).getFieldName(); identity = findIdentityByName(fieldList, idName, javaClass); if (identity != null) { idList.set(i, identity); } } } // Set identities on class descriptor. FieldDescriptor[] ids = new FieldDescriptor[idList.size()]; clsDesc.setIdentities(idList.toArray(ids)); // Set fields on class descriptor. FieldDescriptor[] fields = new FieldDescriptor[fieldList.size()]; clsDesc.setFields(fieldList.toArray(fields)); jdoNature.setTableName(classMapping.getMapTo().getTable()); // Set the field name used for object modification checks. jdoNature.setVersionField(classMapping.getVersion()); extractAndSetAccessMode(jdoNature, classMapping); extractAndAddCacheParams(jdoNature, classMapping, javaClass); extractAndAddNamedQueries(jdoNature, classMapping); extractAndAddNamedNativeQueries(jdoNature, classMapping); extractAndSetKeyGeneratorDescriptor(jdoNature, classMapping.getKeyGenerator()); return clsDesc; } /** * Extract access mode from class mapping and set it at JDO class descriptor. * * @param jdoNature JDO class descriptor to set the access mode on. * @param clsMap Class mapping to extract the access mode from. */ private void extractAndSetAccessMode(final ClassDescriptorJDONature jdoNature, final ClassMapping clsMap) { if (clsMap.getAccess() != null) { jdoNature.setAccessMode(AccessMode.valueOf(clsMap.getAccess().toString())); } } /** * Extract cache parameters from class mapping and add them to JDO class descriptor. * * @param jdoNature JDO class descriptor to add the cache parameters to. * @param clsMap Class mapping to extract the cache parameters from. * @param javaClass Class the cache parameters are defined for. * @throws MappingException If cache type <code>none</code> has been specified for * a class that implements <code>TimeStampable</code> interface. */ private void extractAndAddCacheParams(final ClassDescriptorJDONature jdoNature, final ClassMapping clsMap, final Class javaClass) throws MappingException { jdoNature.addCacheParam(Cache.PARAM_NAME, clsMap.getName()); CacheTypeMapping cacheMapping = clsMap.getCacheTypeMapping(); if (cacheMapping != null) { String type = cacheMapping.getType(); jdoNature.addCacheParam(Cache.PARAM_TYPE, type); Param[] params = cacheMapping.getParam(); for (int i = 0; i < params.length; i++) { jdoNature.addCacheParam(params[i].getName(), params[i].getValue()); } String debug = new Boolean(cacheMapping.getDebug()).toString(); jdoNature.addCacheParam(Cache.PARAM_DEBUG, debug); String capacity = Long.toString(cacheMapping.getCapacity()); jdoNature.addCacheParam(CountLimited.PARAM_CAPACITY, capacity); jdoNature.addCacheParam(TimeLimited.PARAM_TTL, capacity); } } /** * Extract named queries from class mapping and add them to JDO class descriptor. * * @param jdoNature JDO class descriptor to add the named queries to. * @param clsMap Class mapping to extract the named queries from. * @throws MappingException On duplicate query names. */ private void extractAndAddNamedQueries(final ClassDescriptorJDONature jdoNature, final ClassMapping clsMap) throws MappingException { Enumeration namedQueriesEnum = clsMap.enumerateNamedQuery(); while (namedQueriesEnum.hasMoreElements()) { NamedQuery query = (NamedQuery) namedQueriesEnum.nextElement(); String queryName = query.getName(); if (_queryNames.contains(queryName)) { throw new MappingException( "Duplicate entry for named query with name " + queryName); } _queryNames.add(queryName); jdoNature.addNamedQuery(queryName, query.getQuery()); } } /** * Extract named native queries from class mapping and add them to JDO class descriptor. * * @param jdoNature JDO class descriptor to add the named queries to. * @param clsMap Class mapping to extract the named native queries from. * @throws MappingException On duplicate query names. */ private void extractAndAddNamedNativeQueries(final ClassDescriptorJDONature jdoNature, final ClassMapping clsMap) throws MappingException { Enumeration<? extends NamedNativeQuery> namedNativeQueriesEnum = clsMap.enumerateNamedNativeQuery(); while (namedNativeQueriesEnum.hasMoreElements()) { NamedNativeQuery query = namedNativeQueriesEnum.nextElement(); String queryName = query.getName(); if (_queryNames.contains(queryName)) { throw new MappingException( "Duplicate entry for named query with name " + queryName); } _queryNames.add(queryName); jdoNature.addNamedNativeQuery(queryName, query); } } /** * Resolves (or creates) the key generator from a given key generator name. * * Search for an already existing key generator descriptor, e.g. those generated * from the key generator definitions in mapping. If no descriptor can be found * a new one is created and added to the map of class descriptors. Set the * key generator descriptor at the class descriptor. * * @param jdoNature JDO class descriptor to set the key generator descriptor at. * @param keyGeneratorName Name of the key generator. */ private void extractAndSetKeyGeneratorDescriptor(final ClassDescriptorJDONature jdoNature, final String keyGeneratorName) { KeyGeneratorDescriptor keyGeneratorDescriptor = null; if (keyGeneratorName != null) { keyGeneratorDescriptor = _keyGeneratorDescriptors.get(keyGeneratorName); if (keyGeneratorDescriptor == null) { keyGeneratorDescriptor = new KeyGeneratorDescriptor( keyGeneratorName, keyGeneratorName, new Properties()); _keyGeneratorDescriptors.put(keyGeneratorName, keyGeneratorDescriptor); } } jdoNature.setKeyGeneratorDescriptor(keyGeneratorDescriptor); } protected FieldDescriptor findIdentityByName( final List<FieldDescriptor> fldList, final String idName, final Class javaClass) throws MappingException { for (int i = 0; i < fldList.size(); i++) { FieldDescriptor field = fldList.get(i); if (idName.equals(field.getFieldName())) { if (!(field.hasNature(FieldDescriptorJDONature.class.getName()))) { throw new IllegalStateException( "Identity field must be of type JDOFieldDescriptor"); } String[] sqlName = new FieldDescriptorJDONature(field).getSQLName(); if (sqlName == null) { throw new MappingException("mapping.noSqlName", field.getFieldName(), javaClass.getName()); } fldList.remove(i); return field; } } return null; } /** * Walks through all fields of a descriptor and resolves relation * {@link ClassDescriptor}s by using mapping information or, if not * present, resolution by file to support generated * {@link ClassDescriptor}s. Resolved {@link ClassDescriptor}s will be * set as a field's descriptor. * * @param clsDesc * The ClassDescriptor in focus. */ protected void resolveRelations(final ClassDescriptor clsDesc) { FieldDescriptor[] fields = clsDesc.getFields(); for (int i = 0; i < fields.length; ++i) { FieldDescriptor field = fields[i]; ClassDescriptor desc = getDescriptor(field.getFieldType().getName()); // Resolve ClassDescriptor from the file system as well. if (desc == null && !field.getFieldType().isPrimitive()) { ClassResolutionByFile resolutionCommand = new ClassResolutionByFile(); resolutionCommand.addNature(ClassLoaderNature.class.getName()); ClassLoaderNature clNature = new ClassLoaderNature( resolutionCommand); clNature.setClassLoader(getClassLoader()); desc = resolutionCommand.resolve(field.getFieldType()); ((FieldDescriptorImpl) field).setClassDescriptor(desc); } if ((desc != null) && (field instanceof FieldDescriptorImpl)) { ((FieldDescriptorImpl) field).setClassDescriptor(desc); } } } /** * Parse the sql type attribute to build an * array of types, needed to support whitespace inside * parameterized types (see Bug 1045). */ protected String[] getSqlTypes(final FieldMapping fieldMap) { if (fieldMap.getSql() == null) { return new String[0]; } String sqlType = fieldMap.getSql().getType(); if (sqlType == null) { return new String[0]; } ArrayList<String> types = new ArrayList<String>(); int current = 0; int begin = 0; int state = 0; while (current < sqlType.length()) { switch (state) { case 0: if (sqlType.charAt(current) == ' ') { types.add(sqlType.substring(begin, current)); begin = current + 1; } else if (sqlType.charAt(current) == '[') { state = 1; } break; case 1: if (sqlType.charAt(current) == ']') { state = 0; } break; default: break; } current++; } types.add(sqlType.substring(begin, current)); String[] result = new String[types.size()]; return types.toArray(result); } protected TypeInfo getTypeInfo(final Class fieldType, final CollectionHandler colHandler, final FieldMapping fieldMap) throws MappingException { Class internalFieldType = Types.typeFromPrimitive(fieldType); String typeName = null; Class sqlType = null; String sqlParam = null; String[] sqlTypes = getSqlTypes(fieldMap); if ((fieldMap.getSql() != null) && (sqlTypes.length > 0)) { //--TO Check typeName = sqlTypes[0]; sqlType = SQLTypeInfos.sqlTypeName2javaType(definition2type(typeName)); sqlParam = definition2param(typeName); } else { sqlType = internalFieldType; } if (_factory != null) { sqlType = _factory.adjustSqlType(sqlType); } TypeConvertor convertorTo = null; TypeConvertor convertorFrom = null; if (internalFieldType != sqlType) { try { convertorTo = _typeConvertorRegistry.getConvertor( sqlType, internalFieldType, sqlParam); } catch (MappingException ex) { boolean isTypeSafeEnum = false; //-- check for type-safe enum style classes if ((internalFieldType != null) && (!isPrimitive(internalFieldType))) { //-- make sure no default constructor Constructor cons = null; try { cons = internalFieldType.getConstructor(EMPTY_ARGS); if (!Modifier.isPublic(cons.getModifiers())) { cons = null; } } catch (NoSuchMethodException nsmx) { //-- Do nothing } if (cons == null) { //-- make sure a valueOf factory method exists try { Method method = internalFieldType.getMethod(VALUE_OF, STRING_ARG); Class returnType = method.getReturnType(); if ((returnType != null) && internalFieldType.isAssignableFrom(returnType)) { int mods = method.getModifiers(); if (Modifier.isStatic(mods)) { // create individual SQLTypeConverter convertorTo = new String2EnumTypeConvertor( sqlType, internalFieldType, method); Types.addEnumType(internalFieldType); isTypeSafeEnum = true; } } } catch (NoSuchMethodException nsmx) { //-- Do nothing } } if (isTypeSafeEnum) { //-- check if name() method should be used instead of toString() try { Method method = internalFieldType.getMethod(NAME); if (String.class == method.getReturnType()) { convertorFrom = new Enum2StringTypeConvertor( internalFieldType, sqlType, method); } } catch (NoSuchMethodException nsmx) { //-- Do nothing } } } if (!isTypeSafeEnum) { throw new MappingException("mapping.noConvertor", sqlType.getName(), internalFieldType.getName()); } } if (convertorFrom == null) { convertorFrom = _typeConvertorRegistry.getConvertor( internalFieldType, sqlType, sqlParam); } if ((convertorTo != null) && (convertorFrom != null)) { Types.addConvertibleType(internalFieldType); } } return new TypeInfo(internalFieldType, convertorTo, convertorFrom, fieldMap.getRequired(), null, colHandler); } private class String2EnumTypeConvertor extends AbstractSimpleTypeConvertor { private final Method _method; public String2EnumTypeConvertor(final Class<?> fromType, final Class<?> toType, final Method method) { super(fromType, toType); _method = method; } /** * {@inheritDoc} */ public Object convert(final Object object) { try { return _method.invoke(toType(), new Object[] {(String) object}); } catch (Exception ex) { return null; } } } private class Enum2StringTypeConvertor extends AbstractSimpleTypeConvertor { private final Method _method; public Enum2StringTypeConvertor(final Class<?> fromType, final Class<?> toType, final Method method) { super(fromType, toType); _method = method; } /** * {@inheritDoc} */ public Object convert(final Object object) { try { return _method.invoke(object); } catch (Exception ex) { return null; } } } protected FieldDescriptorImpl createFieldDesc(final Class javaClass, final FieldMapping fieldMap) throws MappingException { // If not an SQL field, return a stock field descriptor. Sql sql = fieldMap.getSql(); if (sql == null) { return super.createFieldDesc(javaClass, fieldMap); } String fieldName = fieldMap.getName(); // If the field type is supplied, grab it and use it to locate the // field/accessor. Class fieldType = null; if (fieldMap.getType() != null) { fieldType = resolveType(fieldMap.getType()); } // If the field is declared as a collection, grab the collection type as // well and use it to locate the field/accessor. CollectionHandler colHandler = null; boolean isLazy = fieldMap.getLazy(); if (fieldMap.getCollection() != null) { Class colType = CollectionHandlers.getCollectionType( fieldMap.getCollection().toString()); colHandler = CollectionHandlers.getHandler(colType); if (colType.getName().equals("java.util.Iterator") && isLazy) { String err = "Lazy loading not supported for collection type 'iterator'"; throw new MappingException(err); } if (colType.getName().equals("java.util.Enumeration") && isLazy) { String err = "Lazy loading not supported for collection type 'enumerate'"; throw new MappingException(err); } } TypeInfo typeInfo = getTypeInfo(fieldType, colHandler, fieldMap); ExtendedFieldHandler exfHandler = null; FieldHandler handler = null; //-- check for user supplied FieldHandler if (fieldMap.getHandler() != null) { Class handlerClass = resolveType(fieldMap.getHandler()); if (!FieldHandler.class.isAssignableFrom(handlerClass)) { String err = "The class '" + fieldMap.getHandler() + "' must implement " + FieldHandler.class.getName(); throw new MappingException(err); } //-- get default constructor to invoke. We can't use the //-- newInstance method unfortunately becaue FieldHandler //-- overloads this method Constructor constructor = null; try { constructor = handlerClass.getConstructor(new Class[0]); handler = (FieldHandler) constructor.newInstance(new Object[0]); } catch (Exception except) { String err = "The class '" + handlerClass.getName() + "' must have a default public constructor."; throw new MappingException(err); } //-- ExtendedFieldHandler? if (handler instanceof ExtendedFieldHandler) { exfHandler = (ExtendedFieldHandler) handler; } //-- Fix for Castor JDO from Steve Vaughan, Castor JDO //-- requires FieldHandlerImpl or a ClassCastException //-- will be thrown... [KV 20030131 - also make sure this new handler //-- doesn't use it's own CollectionHandler otherwise //-- it'll cause unwanted calls to the getValue method during //-- unmarshalling] colHandler = typeInfo.getCollectionHandler(); typeInfo.setCollectionHandler(null); handler = new FieldHandlerImpl(handler, typeInfo); typeInfo.setCollectionHandler(colHandler); //-- End Castor JDO fix } boolean generalized = (exfHandler instanceof GeneralizedFieldHandler); //-- if generalized we need to change the fieldType to whatever //-- is specified in the GeneralizedFieldHandler so that the //-- correct getter/setter methods can be found FieldHandler custom = handler; if (generalized) { fieldType = ((GeneralizedFieldHandler) exfHandler).getFieldType(); } if (generalized || (handler == null)) { //-- create TypeInfoRef to get new TypeInfo from call //-- to createFieldHandler TypeInfoReference typeInfoRef = new TypeInfoReference(); typeInfoRef.typeInfo = typeInfo; handler = createFieldHandler(javaClass, fieldType, fieldMap, typeInfoRef); if (custom != null) { ((GeneralizedFieldHandler) exfHandler).setFieldHandler(handler); handler = custom; } else { typeInfo = typeInfoRef.typeInfo; } } String[] sqlName = sql.getName(); String[] sqlTypes = getSqlTypes(fieldMap); int[] sqlTypeNum; if (sqlTypes.length > 0) { sqlTypeNum = new int[sqlTypes.length]; for (int i = 0; i < sqlTypes.length; i++) { String sqlTypeString = definition2type(sqlTypes[i]); Class sqlType = SQLTypeInfos.sqlTypeName2javaType(sqlTypeString); if (_factory != null) { sqlType = _factory.adjustSqlType(sqlType); } sqlTypeNum[i] = SQLTypeInfos.javaType2sqlTypeNum(sqlType); } } else { Class sqlType = typeInfo.getFieldType(); if (_factory != null) { sqlType = _factory.adjustSqlType(sqlType); } sqlTypeNum = new int[] {SQLTypeInfos.javaType2sqlTypeNum(sqlType)}; } // create FieldDescriptor(Impl) instance, and apply JDO nature FieldDescriptorImpl fieldDescriptor = new FieldDescriptorImpl(fieldName, typeInfo, handler, fieldMap.getTransient()); fieldDescriptor.addNature(FieldDescriptorJDONature.class.getName()); fieldDescriptor.setRequired(fieldMap.getRequired()); // If we're using an ExtendedFieldHandler we need to set the FieldDescriptor if (exfHandler != null) { ((FieldHandlerFriend) exfHandler).setFieldDescriptor(fieldDescriptor); } // if SQL mapping declares transient // TODO: cross-check that this should be implemented like this if (sql.getTransient()) { fieldDescriptor.setTransient(true); } // set collection type as specified in mapping file fieldDescriptor.setCollection(fieldMap.getCollection()); fieldDescriptor.setComparator(fieldMap.getComparator()); fieldDescriptor.setCreateMethod(fieldMap.getCreateMethod()); fieldDescriptor.setGetMethod(fieldMap.getGetMethod()); fieldDescriptor.setSetMethod(fieldMap.getSetMethod()); fieldDescriptor.setDirect(fieldMap.getDirect()); // extract values for 'laziness' from field mapping fieldDescriptor.setLazy(isLazy); FieldDescriptorJDONature fieldJdoNature = new FieldDescriptorJDONature(fieldDescriptor); fieldJdoNature.setTypeConvertor(typeInfo.getConvertorFrom()); if (sqlName.length > 0) { fieldJdoNature.setSQLName(sqlName); } fieldJdoNature.setSQLType(sqlTypeNum); fieldJdoNature.setManyTable(sql.getManyTable()); if (sql.getManyKey().length > 0) { fieldJdoNature.setManyKey(sql.getManyKey()); } fieldJdoNature.setDirtyCheck(!SqlDirtyType.IGNORE.equals(sql.getDirty())); fieldJdoNature.setReadOnly(sql.getReadOnly()); fieldJdoNature.setCascading(sql.getCascading()); fieldJdoNature.setTransient(sql.getTransient()); return fieldDescriptor; } }