/* * Copyright 2008 Werner Guttmann * * 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.castor.jdo.jpa.info; import java.util.Map; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.lang.reflect.Method; import javax.persistence.CascadeType; import javax.persistence.FetchType; import javax.persistence.GenerationType; import javax.persistence.TemporalType; import org.castor.cpa.persistence.convertor.EnumToOrdinal; import org.castor.cpa.persistence.convertor.EnumTypeConvertor; import org.castor.cpa.persistence.convertor.EnumTypeConversionHelper; import org.castor.cpa.persistence.convertor.ObjectToString; import org.castor.cpa.persistence.sql.keygen.TableKeyGenerator; import org.castor.jdo.engine.SQLTypeInfos; import org.castor.jdo.jpa.natures.JPAClassNature; import org.castor.jdo.jpa.natures.JPAFieldNature; import org.castor.persist.CascadingType; import org.exolab.castor.jdo.engine.KeyGeneratorDescriptor; import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature; import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.loader.ClassDescriptorImpl; import org.exolab.castor.mapping.loader.FieldDescriptorImpl; 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.types.FieldMappingCollectionType; import org.exolab.castor.xml.ClassDescriptorResolver; import org.exolab.castor.xml.ResolverException; /** * <p> * This Class converts a {@link ClassInfo} and its contained {@link FieldInfo}s * to a {@link ClassDescriptor} with {@link FieldDescriptor}s. * </p> * Working getters for Classes ({@link #convert(ClassInfo)}) and Fields ( * {@link #convert(ClassDescriptor, FieldInfo)}) * * @author Peter Schmidt * @since 1.3 */ public final class InfoToDescriptorConverter { /** * This is a utility class that only offers static functions. */ private InfoToDescriptorConverter() { } /** * This method converts a {@link ClassInfo} to a {@link ClassDescriptorImpl} * . Implemented Features of {@link ClassDescriptorImpl} * <ul> * <li> {@link ClassDescriptorImpl#getExtends()}</li> * <li> {@link ClassDescriptorImpl#getFields()}</li> * <li> {@link ClassDescriptorImpl#getIdentities()}</li> * <li> {@link ClassDescriptorImpl#getIdentity()}</li> * <li> {@link ClassDescriptorImpl#getJavaClass()}</li> * <li> {@link ClassDescriptorImpl#getMapping()}</li> * </ul> * Unimplemented Features of {@link ClassDescriptorImpl} * <ul> * <li> {@link ClassDescriptorImpl#getDepends()}</li> * </ul> * * Implemented Features of {@link ClassDescriptorJDONature} * <ul> * <li> {@link ClassDescriptorJDONature#getTableName()}</li> * <li> {@link ClassDescriptorJDONature#getExtended()}</li> * <li> {@link ClassDescriptorJDONature#getField(String)}</li> * <li> {@link ClassDescriptorJDONature#getCacheParams()}</li> * </ul> * Unimplemented Features of {@link ClassDescriptorJDONature} * <ul> * <li> {@link ClassDescriptorJDONature#getAccessMode()}</li> * <li> {@link ClassDescriptorJDONature#getKeyGeneratorDescriptor()}</li> * <li> {@link ClassDescriptorJDONature#getNamedQueries()}</li> * </ul> * * @see InfoToDescriptorConverter * @param classInfo * The {@link ClassInfo} to convert. * @param cdr * The {@link ClassDescriptorResolver} to ask for needed * {@link ClassDescriptor}s (of extended or related classes). * @param descriptor * A {@link ClassDescriptorImpl} for the class described by the * given {@link ClassInfo}. This will be filled with information! * @throws MappingException * if the class has not a public available default constructor * or the {@link ClassDescriptor} of a related class can not be * found by the {@link ClassDescriptorManager}. */ public static void convert(final ClassInfo classInfo, final ClassDescriptorResolver cdr, final ClassDescriptorImpl descriptor) throws MappingException { /* * parameter checking */ if (classInfo == null) { throw new IllegalArgumentException("ClassInfo must not be null!"); } if (descriptor == null) { throw new IllegalArgumentException( "ClassDescriptor must not be null!"); } if (!classInfo.hasNature(JPAClassNature.class.getName())) { throw new IllegalArgumentException( "ClassInfo must have JPAClassNature on it!"); } if (!Types.isConstructable(classInfo.getDescribedClass(), true)) { throw new MappingException("mapping.classNotConstructable", classInfo.getDescribedClass().getName()); } JPAClassNature nature = new JPAClassNature(classInfo); descriptor.addNature(ClassDescriptorJDONature.class.getName()); ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature( descriptor); /* * set classDescriptor infos */ descriptor.setJavaClass(classInfo.getDescribedClass()); descriptor.setExtends(null); Class<?> extendedClass = classInfo.getExtendedClass(); if (extendedClass != null && extendedClass != Object.class) { ClassDescriptor extendedClassDescriptor = null; try { extendedClassDescriptor = cdr.resolve(extendedClass); if (extendedClassDescriptor == null) { throw new MappingException( "Unable to resolve extended class " + extendedClass.getName() + " in " + classInfo.getDescribedClass().getName()); } } catch (ResolverException e) { throw new MappingException("Unable to resolve extended class " + extendedClass.getName() + " in " + classInfo.getDescribedClass().getName(), e); } if (new ClassDescriptorJDONature(extendedClassDescriptor).hasMappedSuperclass()) { ClassInfo extendedClassInfo = ClassInfoRegistry.getClassInfo(extendedClass); for (FieldInfo fieldInfo : extendedClassInfo.getKeyFieldInfos()) { classInfo.addKey(fieldInfo); } for (FieldInfo fieldInfo : extendedClassInfo.getFieldInfos()) { classInfo.addFieldInfo(fieldInfo); } } else { descriptor.setExtends(extendedClassDescriptor); if (extendedClassDescriptor.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature jdoClassNature = new ClassDescriptorJDONature(extendedClassDescriptor); jdoClassNature.addExtended(descriptor); } } } /* * TODO: NOT IMPLEMENTED */ descriptor.setDepends(null); descriptor.setJavaClass(classInfo.getDescribedClass()); /* * set ClassDescriptorJDONature infos */ // Table name String tableName = nature.getTableName(); if ((tableName == null) || (tableName.trim().length() == 0)) { tableName = nature.getEntityName(); } jdoNature.setTableName(tableName); jdoNature .addCacheParam("name", classInfo.getDescribedClass().getName()); Properties cacheProperties = nature.getCacheProperties(); if (cacheProperties != null) { for (Object propertyKey : cacheProperties.keySet()) { String key = (String) propertyKey; jdoNature.addCacheParam(key, cacheProperties.getProperty(key)); } } /* * TODO: NOT IMPLEMENTED */ jdoNature.setAccessMode(null); // Set abstract if present jdoNature.setAbstract(nature.hasMappedSuperclass()); // Add named queries if present. final Map<String, String> namedQuery = nature.getNamedQuery(); if (namedQuery != null && namedQuery.size() > 0) { for (Map.Entry<String, String> entry : namedQuery.entrySet()) { jdoNature.addNamedQuery(entry.getKey(), entry.getValue()); } } // Add named native queries if present. final Map<String, String> namedNativeQueryMap = nature.getNamedNativeQuery(); if (namedNativeQueryMap != null && namedNativeQueryMap.size() > 0) { for (Map.Entry<String,String> entry: namedNativeQueryMap.entrySet()){ org.exolab.castor.mapping.xml.NamedNativeQuery namedNativeQuery = new org.exolab.castor.mapping.xml.NamedNativeQuery(); namedNativeQuery.setName(entry.getKey()); namedNativeQuery.setQuery(entry.getValue()); namedNativeQuery.setResultClass(classInfo.getDescribedClass().getName()); jdoNature.addNamedNativeQuery(namedNativeQuery.getName(), namedNativeQuery); } } /* * generate and set FieldDescriptors for identities */ FieldDescriptor[] keys = new FieldDescriptor[classInfo .getKeyFieldCount()]; int i = 0; for (FieldInfo fieldInfo : classInfo.getKeyFieldInfos()) { JPAFieldNature jpaKeyInfo = new JPAFieldNature(fieldInfo); if (jpaKeyInfo.getGeneratedValueStrategy() != null) { KeyGeneratorDescriptor generatorDescriptor; generatorDescriptor = describeKeyGenerator(fieldInfo, jpaKeyInfo); jdoNature.setKeyGeneratorDescriptor(generatorDescriptor); } keys[i] = convert(descriptor, fieldInfo, cdr); i++; } descriptor.setIdentities(keys); /* * generate and set FieldDescriptors for fields */ FieldDescriptor[] fields = new FieldDescriptor[classInfo .getFieldCount()]; i = 0; for (FieldInfo fieldInfo : classInfo.getFieldInfos()) { fields[i] = convert(descriptor, fieldInfo, cdr); i++; } descriptor.setFields(fields); defineVersionField(classInfo, jdoNature); } private static void defineVersionField(final ClassInfo classInfo, ClassDescriptorJDONature jdoNature) { JPAVersionManager manager = JPAVersionManager.getInstance(); String versionField = manager.get(classInfo.getDescribedClass()); if (versionField != null) { jdoNature.setVersionField(versionField); } } private static KeyGeneratorDescriptor describeKeyGenerator( FieldInfo fieldInfo, JPAFieldNature jpaKeyInfo) { GenerationType strategy = jpaKeyInfo.getGeneratedValueStrategy(); String strategyName = strategy.toString(); Properties generatorParameters = new Properties(); String generatorName; JPAKeyGeneratorManager manager = JPAKeyGeneratorManager.getInstance(); KeyGeneratorDescriptor generatorDescriptor; switch (strategy) { case SEQUENCE: generatorName = jpaKeyInfo.getGeneratedValueGenerator(); JPASequenceGeneratorDescriptor sequenceGeneratorDescriptor = (JPASequenceGeneratorDescriptor)manager.get(generatorName); String sequenceName = sequenceGeneratorDescriptor.getSequenceName(); if (!"".equals(sequenceName)) { generatorParameters.put("sequence", sequenceName); } break; case AUTO: strategyName = GenerationType.TABLE.toString(); JPATableGeneratorDescriptor autoGeneratorDescriptor = (JPATableGeneratorDescriptor)manager.getAuto(); autoGeneratorDescriptor.setPrimaryKeyType(fieldInfo.getFieldType()); generatorParameters.put(TableKeyGenerator.DESCRIPTOR_KEY, autoGeneratorDescriptor); break; case TABLE: generatorName = jpaKeyInfo.getGeneratedValueGenerator(); JPATableGeneratorDescriptor tableGeneratorDescriptor = (JPATableGeneratorDescriptor)manager.get(generatorName); tableGeneratorDescriptor.setPrimaryKeyType(fieldInfo.getFieldType()); generatorParameters.put(TableKeyGenerator.DESCRIPTOR_KEY, tableGeneratorDescriptor); break; case IDENTITY: strategyName = strategy.toString().toUpperCase(); break; default: break; } generatorDescriptor = new KeyGeneratorDescriptor(strategyName, strategyName, generatorParameters); return generatorDescriptor; } /** * This method converts a {@link FieldInfo} to a {@link FieldDescriptorImpl} * . Implemented Features of {@link ClassDescriptorImpl} * * Implemented Features of {@link FieldDescriptorImpl} * <ul> * <li> {@link FieldDescriptorImpl#getContainingClassDescriptor()}</li> * <li> {@link FieldDescriptorImpl#getFieldName()}</li> * <li> {@link FieldDescriptorImpl#getFieldType()}</li> * <li> {@link FieldDescriptorImpl#getHandler()}</li> * <li> {@link FieldDescriptorImpl#isTransient()}</li> * <li> {@link FieldDescriptorImpl#isIdentity()}</li> * <li> {@link FieldDescriptor#isMultivalued()}</li> * <li> {@link FieldDescriptor#isRequired()}</li> * <li> {@link FieldDescriptor#getClassDescriptor()}</li> * </ul> * Unimplemented Features of {@link FieldDescriptorImpl} * <ul> * <li> {@link FieldDescriptor#isImmutable()}</li> * </ul> * * Implemented Features of {@link FieldDescriptorJDONature} * <ul> * <li> {@link FieldDescriptorJDONature#getSQLName()}</li> * <li> {@link FieldDescriptorJDONature#getSQLType()}</li> * <li> {@link FieldDescriptorJDONature#getManyKey()}</li> * <li> {@link FieldDescriptorJDONature#getManyTable()}</li> * </ul> * Unimplemented Features of {@link FieldDescriptorJDONature} * <ul> * <li> {@link FieldDescriptorJDONature#isDirtyCheck()}</li> * <li> {@link FieldDescriptorJDONature#isReadonly()}</li> * <li> {@link FieldDescriptorJDONature#getConvertor()}</li> * </ul> * * @param parent * The {@link ClassDescriptor} of the Class containing the field * described by the given {@link FieldInfo}. * @param fieldInfo * The {@link FieldInfo} to convert. * @param cdr * The {@link ClassDescriptorResolver} to ask for needed * {@link ClassDescriptor}s (of extended, used classes). * @return A {@link FieldDescriptorImpl} for the field described by the * given {@link FieldInfo}. * @throws MappingException * if the Field is not accessible. * @see InfoToDescriptorConverter */ private static FieldDescriptorImpl convert( final ClassDescriptorImpl parent, final FieldInfo fieldInfo, final ClassDescriptorResolver cdr) throws MappingException { /* * Instantiate Fielddescriptor, nature, etc. */ if (!fieldInfo.hasNature(JPAFieldNature.class.getName())) { throw new IllegalArgumentException( "FieldInfo must have JPAFieldNature on it!"); } JPAFieldNature jpaNature = new JPAFieldNature(fieldInfo); String fieldName = fieldInfo.getFieldName(); TypeInfo typeInfo = createTypeInfo(fieldInfo, jpaNature); FieldHandler handler = createFieldHandler(fieldInfo, typeInfo); boolean isTransient = jpaNature.isTransient(); FieldDescriptorImpl fieldDescriptor = new FieldDescriptorImpl( fieldName, typeInfo, handler, isTransient); fieldDescriptor.addNature(FieldDescriptorJDONature.class.getName()); FieldDescriptorJDONature jdoNature = new FieldDescriptorJDONature( fieldDescriptor); /* * Generate values and prerequisites */ // Field type Class<?> javaType = org.exolab.castor.mapping.loader.Types .typeFromPrimitive(typeInfo.getFieldType()); int sqlType = SQLTypeInfos.javaType2sqlTypeNum(javaType); if (fieldInfo.getFieldType().isEnum()) { // Serializing enum types. sqlType = jpaNature.isStringEnumType() ? java.sql.Types.VARCHAR : java.sql.Types.INTEGER; } else if (jpaNature.isLob()) { sqlType = typeInfo.getFieldType() == String.class ? java.sql.Types.CLOB : java.sql.Types.BLOB; } else { final TemporalType temporalType = jpaNature.getTemporalType(); if (temporalType != null) { switch (temporalType) { case DATE: sqlType = java.sql.Types.DATE; break; case TIME: sqlType = java.sql.Types.TIME; break; case TIMESTAMP: sqlType = java.sql.Types.TIMESTAMP; break; default: break; } } } // ManyToMany JoinTable definition => if this field has a ManyToMany // relation, everything is defined from now on, because we do not // support mapping defaults and the ClassInfoBuilder already checks for // complete definition. if (jpaNature.isManyToManyInverseCopy()) { ClassInfo relatedClass = ClassInfoBuilder.buildClassInfo(jpaNature .getRelationTargetEntity()); String relatedFieldName = jpaNature.getRelationMappedBy(); FieldInfo relatedField = relatedClass .getFieldInfoByName(relatedFieldName); JPAFieldNature relatedFieldNature = new JPAFieldNature(relatedField); jpaNature.setJoinTableCatalog(relatedFieldNature .getJoinTableCatalog()); jpaNature.setJoinTableName(relatedFieldNature.getJoinTableName()); jpaNature.setJoinTableSchema(relatedFieldNature .getJoinTableSchema()); jpaNature.setJoinTableInverseJoinColumns(relatedFieldNature .getJoinTableJoinColumns()); jpaNature.setJoinTableJoinColumns(relatedFieldNature .getJoinTableInverseJoinColumns()); } /* * FieldDescriptor value setting */ fieldDescriptor.setContainingClassDescriptor(parent); fieldDescriptor.setFieldType(typeInfo.getFieldType()); fieldDescriptor.setIdentity(jpaNature.isId()); if (hasFieldRelation(jpaNature)) { if (jpaNature.getCascadeTypes() != null) { final StringBuilder cascading = new StringBuilder(); final List<CascadeType> cascadeTypes = Arrays.asList( jpaNature.getCascadeTypes()); if (cascadeTypes.contains(CascadeType.ALL)) { cascading.append(CascadingType.CREATE.name()); cascading.append(" "); cascading.append(CascadingType.DELETE.name()); } else { if (cascadeTypes.contains(CascadeType.PERSIST)) { cascading.append(CascadingType.CREATE.name()); cascading.append(" "); } if (cascadeTypes.contains(CascadeType.REMOVE)) { cascading.append(CascadingType.DELETE.name()); } } jdoNature.setCascading(cascading.toString()); } // descriptor.setClassDescriptor try { fieldDescriptor.setClassDescriptor(cdr.resolve(jpaNature .getRelationTargetEntity())); } catch (ResolverException e) { throw new MappingException( "Can not resolve ClassDescriptor for Class " + jpaNature.getRelationTargetEntity().getName() + " needed by " + fieldInfo.getDeclaringClassInfo() .getDescribedClass().getName() + "#" + fieldName); } } // descriptor.setRequired fieldDescriptor.setRequired(createRequired(jpaNature)); // fieldDescriptor.setMultivalued fieldDescriptor.setMultivalued(false); if (jpaNature.isOneToMany() || jpaNature.isManyToMany()) { fieldDescriptor.setMultivalued(true); } /* * TODO: NOT IMPLEMENTED */ fieldDescriptor.setImmutable(false); /* * FieldDescriptorJDONature value setting */ jdoNature.setSQLType(new int[] { sqlType }); jdoNature.setSQLName(createSQLName(fieldName, jpaNature, fieldDescriptor.getClassDescriptor())); jdoNature.setManyKey(createManyKey(fieldName, jpaNature, fieldDescriptor.getClassDescriptor(), parent)); // jdoNature.setManyTable (N:M only) if (jpaNature.isManyToMany()) { jdoNature.setManyTable(jpaNature.getJoinTableName()); } /* * TODO: NOT IMPLEMENTED */ jdoNature.setTypeConvertor(null); jdoNature.setReadOnly(false); /* * TODO: how to set this to false? How should JPA tell Castor to use or * not use dirtyCheck */ jdoNature.setDirtyCheck(true); fieldDescriptor.setFieldName(fieldName); // fieldMapping.setIdentity(fieldDescriptor.isIdentity()); fieldDescriptor.setLazy(createFMLazy(jpaNature)); fieldDescriptor.setDirect(false); // field access => not supported fieldDescriptor.setGetMethod(fieldInfo.getGetterMethod().getName()); fieldDescriptor.setSetMethod(fieldInfo.getSetterMethod().getName()); fieldDescriptor.setCollection(createColletionType(jpaNature)); setJDONatureValues(fieldName, jpaNature, fieldDescriptor.getClassDescriptor(), sqlType, parent, fieldDescriptor); fieldDescriptor.setFieldType(typeInfo.getFieldType()); return fieldDescriptor; } /** * Create a {@link FieldHandler} for the instantiating the * {@link FieldDescriptorImpl}. * * @param fieldInfo * The {@link FieldInfo} holding Java specific information and * the nature. * @param typeInfo * The {@link TypeInfo} holding information about the java Type. * @return a {@link FieldHandler} for the instantiating the * {@link FieldDescriptorImpl}. * * @throws MappingException * If the get or set method are not public, are static, or do * not specify the proper types. */ private static FieldHandler createFieldHandler(final FieldInfo fieldInfo, final TypeInfo typeInfo) throws MappingException { FieldHandlerImpl fieldHandler = null; fieldHandler = new FieldHandlerImpl(fieldInfo.getFieldName(), null, null, fieldInfo.getGetterMethod(), fieldInfo.getSetterMethod(), typeInfo); return fieldHandler; } /** * Create a {@link TypeInfo} according to information from the given * {@link FieldInfo} and its {@link JPAFieldNature}. This is used to get the * fields type and to create the {@link FieldHandler}. * * @param fieldInfo * The {@link FieldInfo} holding Java specific information and * the nature. * @param jpaNature * The nature holding JPA information. * @return a {@link TypeInfo} according to information from the given * {@link FieldInfo} and its {@link JPAFieldNature}. * @throws org.exolab.castor.mapping.MappingException on enum mapping error */ private static TypeInfo createTypeInfo(final FieldInfo fieldInfo, final JPAFieldNature jpaNature) throws MappingException { final Class<?> fieldType = fieldInfo.getFieldType(); if (hasFieldRelation(jpaNature)) { return new TypeInfo(jpaNature.getRelationTargetEntity()); } else if (fieldType.isEnum()) { try { // Apply custom enum type conversion. final EnumTypeConversionHelper enumTypeConversionHelper = new EnumTypeConversionHelper(fieldType); final Method method = jpaNature.isStringEnumType() ? fieldType.getMethod("valueOf", String.class) : enumTypeConversionHelper.getClass().getMethod( "getEnumConstantValueByOrdinal", int.class); final EnumTypeConvertor typeConvertor = new EnumTypeConvertor( jpaNature.isStringEnumType() ? String.class : int.class, fieldType, method); final TypeInfo typeInfo = new TypeInfo(fieldType, typeConvertor, jpaNature.isStringEnumType() ? new ObjectToString() : new EnumToOrdinal(), createRequired(jpaNature), null, null); Types.addEnumType(fieldType); // Register type accordingly. Types.addConvertibleType(fieldType); return typeInfo; } catch (NoSuchMethodException ex) { throw new MappingException(String.format( "Problem occurred mapping enum `%s`: %s", fieldType, ex.getMessage()), ex); } } return new TypeInfo(fieldInfo.getFieldType()); } /** * Create the right parameter values for * {@link FieldDescriptorImpl#setRequired(boolean)} according to given JPA * specific information. * * @param jpaNature * The nature holding JPA information. * @return the right parameter values for * {@link FieldDescriptorImpl#setRequired(boolean)}. */ private static boolean createRequired(final JPAFieldNature jpaNature) { if (jpaNature.getColumnNullable() != null) { return !jpaNature.getColumnNullable().booleanValue(); } if (jpaNature.isId()) { return true; } if (jpaNature.isOneToMany()) { return true; } if (jpaNature.isManyToMany()) { return true; } if (!jpaNature.isRelationOptional()) { return true; } if (!jpaNature.isBasicOptional()) { return true; } return false; } /** * Create the right parameter values for * {@link FieldDescriptor#setCollection(FieldMappingCollectionType)} according * to given JPA specific information. * * @param jpaNature * The nature holding JPA information. * @return the right parameter values for * {@link FieldDescriptor#setCollection(FieldMappingCollectionType)}. */ private static FieldMappingCollectionType createColletionType( final JPAFieldNature jpaNature) { if (jpaNature.getRelationCollectionType() != null) { // OneToMany or ManyToMany String collectionTypeName = jpaNature.getRelationCollectionType() .getSimpleName().toLowerCase(); return FieldMappingCollectionType.fromValue(collectionTypeName); } return null; } /** * Set the correct values on the JDO nature of the FieldDescriptor so that * the information given by the means of JPA annotations is matched. * * This method may (in case of a default mapped OneToMany relation) access * the parents {@link ClassDescriptorImpl#getIdentity()}, so identities have * to be converted before relational fields are, to avoid loops. * * @param fieldName * The name of the field. * @param jpaNature * The nature holding JPA information. * @param fieldClassDescriptor * This is needed for OneToOne and ManyToOne relations only! * @param sqlType * the SQL type representing the java type. * @param parentClassDescriptor * The {@link ClassDescriptorImpl} of the field owning class to * get the identity field (see above). * @throws MappingException * if the sqlType is not recognized. */ private static void setJDONatureValues(final String fieldName, final JPAFieldNature jpaNature, final ClassDescriptor fieldClassDescriptor, final int sqlType, final ClassDescriptorImpl parentClassDescriptor, final FieldDescriptor fieldDescriptor) throws MappingException { if (!fieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) { FieldDescriptorJDONature nature = new FieldDescriptorJDONature(fieldClassDescriptor); String[] sqlNames = createSQLName(fieldName, jpaNature, fieldClassDescriptor); // Sql fieldSql = new Sql(); if (!hasFieldRelation(jpaNature)) { // for non-relational field // for (String sqlName : sqlNames) { // fieldSql.addName(sqlName); // } nature.setSQLName(sqlNames); // fieldSql.setType(SQLTypeInfos.sqlTypeNum2sqlTypeName(sqlType)); nature.setSQLType(new int[] { sqlType }); } else if (isXToOne(jpaNature)) { // for 1:1 or N:1 (owning side) // for (String sqlName : sqlNames) { // fieldSql.addName(sqlName); // fieldSql.addManyKey(sqlName); // } nature.setSQLName(sqlNames); nature.setManyKey(sqlNames); } else { // for 1:N or M:N String[] manyKeys = createManyKey(fieldName, jpaNature, fieldClassDescriptor, parentClassDescriptor); // for (String sqlName : manyKeys) { // fieldSql.addName(sqlName); // fieldSql.addManyKey(sqlName); // } nature.setSQLName(manyKeys); nature.setManyKey(manyKeys); } } // return fieldSql; } /** * Create the right parameter values for * {@link FieldDescriptorImpl#setLazy(boolean)} according to given JPA specific * information. * * @param jpaNature * The nature holding JPA information. * @return the right parameter values for * {@link FieldDescriptorImpl#setLazy(boolean)}. */ private static boolean createFMLazy(final JPAFieldNature jpaNature) { if (jpaNature.isRelationLazyFetch()) { return true; } if (FetchType.LAZY.equals(jpaNature.getBasicFetch())) { return true; } return false; } /** * Create the right parameter values for * {@link FieldDescriptorJDONature#setManyKey(String[])} according to given * JPA specific information. This method may (in case of a default mapped * OneToMany relation) access the parents * {@link ClassDescriptorImpl#getIdentity()}, so identities have to be * converted before relational fields are to avoid loops. * * @param fieldName * The name of the field. * @param jpaNature * The nature holding JPA information. * @param fieldClassDescriptor * This is needed for OneToOne and ManyToOne relations only! * @param parentClassDescriptor * The {@link ClassDescriptorImpl} of the field owning class to * get the identity field (see above). * @return the right parameter values for * {@link FieldDescriptorJDONature#setManyKey(String[])}. */ private static String[] createManyKey(final String fieldName, final JPAFieldNature jpaNature, final ClassDescriptor fieldClassDescriptor, final ClassDescriptorImpl parentClassDescriptor) { // for 1:1 or N:1 (owning side) if (isXToOne(jpaNature)) { return createSQLName(fieldName, jpaNature, fieldClassDescriptor); } // for 1:N if (jpaNature.isOneToMany()) { String[] sqlManyKey = new String[1]; // Column name if defined sqlManyKey[0] = jpaNature.getJoinColumnName(); // default (<other field name>_<PK-ColName of this Entity>) if not // defined if ((sqlManyKey[0] == null) || (sqlManyKey[0].trim().length() == 0)) { // if bi-directional if (jpaNature.getRelationMappedBy() != null) { sqlManyKey[0] = jpaNature.getRelationMappedBy() + "_" + parentClassDescriptor.getIdentity() .getFieldName(); } // TODO: unidirectional Mapping (jointable, etc.) } return sqlManyKey; } if (jpaNature.isManyToMany()) { String[] sqlManyKey = new String[1]; // Column name if defined sqlManyKey[0] = jpaNature.getJoinTableJoinColumns()[0].name(); // TODO: maybe this should be referencedColumnName() instead... // needs testing! // If this is not defined, defaults are applying // defaults are not supported by Castor if ((sqlManyKey[0] == null) || (sqlManyKey[0].trim().length() == 0)) { throw new IllegalStateException( "Could not find JoinColumn definition on M:N relation! " + "This must be defined on either sides of the relation!"); } return sqlManyKey; } // for non-relational field return null; } /** * Create the right parameter values for * {@link FieldDescriptorJDONature#setSQLName(String[])} according to given * JPA specific information. * * @param fieldName * The name of the field. * @param jpaNature * The nature holding JPA information. * @param fieldClassDescriptor * This is needed for OneToOne and ManyToOne relations only! * @return the right parameter values for * {@link FieldDescriptorJDONature#setSQLName(String[])}. */ private static String[] createSQLName(final String fieldName, final JPAFieldNature jpaNature, final ClassDescriptor fieldClassDescriptor) { String[] sqlName = new String[1]; if (!hasFieldRelation(jpaNature)) { // for non-relational field // Column name if defined sqlName[0] = jpaNature.getColumnName(); // default (field name) if not defined if ((sqlName[0] == null) || (sqlName[0].trim().length() == 0)) { sqlName[0] = fieldName; } } else if (isXToOne(jpaNature)) { // for 1:1 or N:1 (owning side) // Column name if defined sqlName[0] = jpaNature.getJoinColumnName(); // default (<Fieldname>_<PK-ColName of other Entity>) if not defined if ((sqlName[0] == null) || (sqlName[0].trim().length() == 0)) { sqlName[0] = fieldName + "_" + fieldClassDescriptor.getIdentity().getFieldName(); } } else { // for 1:N (not owning side) sqlName = null; } return sqlName; } /** * Little helper to ease reading conditionals. Returns true if the field is * owning a relation in the database (i.e. is part of a OneToOne or * ManyToOne relation). * * @param jpaNature * The nature holding JPA information. * @return true if the field described by the nature is owning the relation * to another one. */ private static boolean isXToOne(final JPAFieldNature jpaNature) { return jpaNature.isOneToOne() || jpaNature.isManyToOne(); } /** * Little helper to ease reading conditionals. Returns true if the field has * JPA relation specific information (i.e. is part of a OneToOne, OneToMany, * ManyToOne or ManyToMany relation). * * @param jpaNature * The nature holding JPA information. * @return true if the field described by the nature is related to another * one. */ private static boolean hasFieldRelation(final JPAFieldNature jpaNature) { return jpaNature.isOneToOne() || jpaNature.isManyToOne() || jpaNature.isOneToMany() || jpaNature.isManyToMany(); } }