blob: 1a68e2dbd26c5eadb45a9deb3db6bcaea198e815 [file] [log] [blame]
package org.apache.archiva.redback.components.modello.jpox;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import org.apache.archiva.redback.components.modello.db.SQLReservedWords;
import org.apache.archiva.redback.components.modello.db.SQLReservedWords.KeywordSource;
import org.apache.archiva.redback.components.modello.jpox.metadata.JPoxAssociationMetadata;
import org.apache.archiva.redback.components.modello.jpox.metadata.JPoxClassMetadata;
import org.apache.archiva.redback.components.modello.jpox.metadata.JPoxFieldMetadata;
import org.apache.archiva.redback.components.modello.jpox.metadata.JPoxModelMetadata;
import org.apache.archiva.redback.components.modello.plugin.store.metadata.StoreAssociationMetadata;
import org.apache.archiva.redback.components.modello.plugin.store.metadata.StoreFieldMetadata;
import org.codehaus.modello.ModelloException;
import org.codehaus.modello.ModelloParameterConstants;
import org.codehaus.modello.model.Model;
import org.codehaus.modello.model.ModelAssociation;
import org.codehaus.modello.model.ModelClass;
import org.codehaus.modello.model.ModelField;
import org.codehaus.modello.plugin.AbstractModelloGenerator;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
import org.codehaus.plexus.util.xml.XMLWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
* @version $Id: JPoxJdoMappingModelloGenerator.java 829 2007-03-22 14:32:42Z joakime $
* @plexus.component role="org.codehaus.modello.plugin.ModelloGenerator"
* role-hint="jpox-jdo-mapping"
*/
public class JPoxJdoMappingModelloGenerator
extends AbstractModelloGenerator
{
private static final char EOL = '\n';
private static final String ERROR_LINE = "----------------------------------------------------------------";
private final static Map<String, String> PRIMITIVE_IDENTITY_MAP;
private final static List<String> IDENTITY_TYPES;
private final static List<String> VALUE_STRATEGY_LIST;
/**
* @plexus.requirement
*/
private SQLReservedWords sqlReservedWords;
private String valueStrategyOverride;
private String objectIdClassOverride;
static
{
PRIMITIVE_IDENTITY_MAP = new HashMap<>();
// TODO: These should be the fully qualified class names
PRIMITIVE_IDENTITY_MAP.put( "short", "javax.jdo.identity.ShortIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "Short", "javax.jdo.identity.ShortIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "int", "javax.jdo.identity.IntIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "Integer", "javax.jdo.identity.IntIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "long", "javax.jdo.identity.LongIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "Long", "javax.jdo.identity.LongIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "String", "javax.jdo.identity.StringIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "char", "javax.jdo.identity.CharIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "Character", "javax.jdo.identity.CharIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "byte", "javax.jdo.identity.ByteIdentity" );
PRIMITIVE_IDENTITY_MAP.put( "Byte", "javax.jdo.identity.ByteIdentity" );
IDENTITY_TYPES = new ArrayList<>();
IDENTITY_TYPES.add( "application" );
IDENTITY_TYPES.add( "datastore" );
IDENTITY_TYPES.add( "nondurable" );
VALUE_STRATEGY_LIST = new ArrayList<>();
//VALUE_STRATEGY_LIST.add( "off" ); -- this isn't really valid. It turns it on. We use it internally to set an explicit null
VALUE_STRATEGY_LIST.add( "native" );
VALUE_STRATEGY_LIST.add( "sequence" );
VALUE_STRATEGY_LIST.add( "identity" );
VALUE_STRATEGY_LIST.add( "increment" );
VALUE_STRATEGY_LIST.add( "uuid-string" );
VALUE_STRATEGY_LIST.add( "uuid-hex" );
VALUE_STRATEGY_LIST.add( "datastore-uuid-hex" );
VALUE_STRATEGY_LIST.add( "max" );
VALUE_STRATEGY_LIST.add( "auid" );
}
protected void initialize( Model model, Properties parameters )
throws ModelloException
{
super.initialize( model, parameters );
valueStrategyOverride = parameters.getProperty( "JPOX.override.value-strategy" );
objectIdClassOverride = parameters.getProperty( "JPOX.override.objectid-class" );
}
public void generate( Model model, Properties properties )
throws ModelloException
{
initialize( model, properties );
// ----------------------------------------------------------------------
// Generate the JDO files
// ----------------------------------------------------------------------
try
{
String fileName = properties.getProperty( ModelloParameterConstants.FILENAME, "package.jdo" );
JPoxModelMetadata metadata = (JPoxModelMetadata) model.getMetadata( JPoxModelMetadata.ID );
File packageJdo = null;
if ( metadata.isMappingInPackage() )
{
// Use package name.
String packageName = model.getDefaultPackageName( isPackageWithVersion(), getGeneratedVersion() );
String dir = StringUtils.replace( packageName, '.', '/' );
File directory = new File( getOutputDirectory(), dir );
packageJdo = new File( directory, fileName );
}
else
{
// Use the specified location.
File directory = getOutputDirectory();
packageJdo = new File( directory, fileName );
}
File parent = packageJdo.getParentFile();
if ( !parent.exists() )
{
if ( !parent.mkdirs() )
{
throw new ModelloException(
"Error while creating parent directories for the file " + "'" + packageJdo.getAbsolutePath()
+ "'."
);
}
}
generatePackageJdo( packageJdo, model );
}
catch ( IOException e )
{
throw new ModelloException( "Error while writing package.jdo.", e );
}
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
private void generatePackageJdo( File file, Model model )
throws IOException, ModelloException
{
try (FileOutputStream fileOutputStream = new FileOutputStream( file ))
{
try (OutputStreamWriter fileWriter = new OutputStreamWriter( fileOutputStream, "UTF-8" ))
{
try (PrintWriter printWriter = new PrintWriter( fileWriter ))
{
XMLWriter writer = new PrettyPrintXMLWriter( printWriter );
Map<String, List<ModelClass>> classes = new HashMap<>();
for ( Iterator<ModelClass> it = model.getClasses( getGeneratedVersion() ).iterator();
it.hasNext(); )
{
ModelClass modelClass = it.next();
JPoxClassMetadata jpoxMetadata =
JPoxClassMetadata.class.cast( modelClass.getMetadata( JPoxClassMetadata.ID ) );
if ( !jpoxMetadata.isEnabled() )
{
// Skip generation of those classes that are not enabled for the jpox plugin.
continue;
}
String packageName = modelClass.getPackageName( isPackageWithVersion(), getGeneratedVersion() );
List<ModelClass> list = classes.get( packageName );
if ( list == null )
{
list = new ArrayList<>();
}
list.add( modelClass );
classes.put( packageName, list );
}
printWriter.println( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" );
printWriter.println();
printWriter.println( "<!DOCTYPE jdo PUBLIC" );
printWriter.println( " \"-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 2.0//EN\"" );
printWriter.println( " \"http://java.sun.com/dtd/jdo_2_0.dtd\">" );
printWriter.println();
writer.startElement( "jdo" );
for ( Map.Entry<String, List<ModelClass>> entry : classes.entrySet() )
{
List<ModelClass> list = entry.getValue();
if ( list.size() == 0 )
{
continue;
}
String packageName = entry.getKey();
writer.startElement( "package" );
writer.addAttribute( "name", packageName );
for ( ModelClass modelClass : list )
{
writeClass( writer, modelClass );
}
if ( packageName.equals(
model.getDefaultPackageName( isPackageWithVersion(), getGeneratedVersion() ) ) )
{
writeModelloMetadataClass( writer );
}
writer.endElement(); // package
}
writer.endElement(); // jdo
printWriter.println();
}
}
}
}
private void writeClass( XMLWriter writer, ModelClass modelClass )
throws ModelloException
{
JPoxClassMetadata jpoxMetadata = (JPoxClassMetadata) modelClass.getMetadata( JPoxClassMetadata.ID );
if ( !jpoxMetadata.isEnabled() )
{
// Skip generation of those classes that are not enabled for the jpox plugin.
return;
}
writer.startElement( "class" );
writer.addAttribute( "name", modelClass.getName() );
ModelClass persistenceCapableSuperclass = null;
if ( modelClass.hasSuperClass() && modelClass.isInternalSuperClass() )
{
persistenceCapableSuperclass = getModel().getClass( modelClass.getSuperClass(), getGeneratedVersion() );
}
if ( persistenceCapableSuperclass != null )
{
String superPackageName =
persistenceCapableSuperclass.getPackageName( isPackageWithVersion(), getGeneratedVersion() );
writer.addAttribute( "persistence-capable-superclass",
superPackageName + "." + persistenceCapableSuperclass.getName() );
}
writer.addAttribute( "detachable", String.valueOf( jpoxMetadata.isDetachable() ) );
writer.addAttribute( "table", getTableName( modelClass, jpoxMetadata ) );
// ----------------------------------------------------------------------
// If this class has a primary key field mark make jpox manage the id
// as a autoincrement variable
// ----------------------------------------------------------------------
List fields = Collections.unmodifiableList( modelClass.getFields( getGeneratedVersion() ) );
// TODO: for now, assume that any primary key will be set in the super class
// While it should be possible to have abstract super classes and have the
// key defined in the sub class this is not implemented yet.
boolean needInheritance = false;
if ( persistenceCapableSuperclass == null )
{
if ( StringUtils.isNotEmpty( jpoxMetadata.getIdentityType() ) )
{
String identityType = jpoxMetadata.getIdentityType();
if ( !IDENTITY_TYPES.contains( identityType ) )
{
throw new ModelloException(
"The JDO mapping generator does not support the specified " + "class identity type '"
+ identityType + "'. " + "Supported types: " + IDENTITY_TYPES
);
}
writer.addAttribute( "identity-type", identityType );
}
else if ( isInstantionApplicationType( modelClass ) )
{
writer.addAttribute( "identity-type", "application" );
}
}
else
{
needInheritance = true;
}
if ( objectIdClassOverride != null )
{
if ( StringUtils.isNotEmpty( objectIdClassOverride ) )
{
writer.addAttribute( "objectid-class", jpoxMetadata.getIdentityClass() );
}
}
else if ( StringUtils.isNotEmpty( jpoxMetadata.getIdentityClass() ) )
{
// Use user provided objectId class.
writer.addAttribute( "objectid-class", jpoxMetadata.getIdentityClass() );
}
else
{
// Calculate the objectId class.
List primaryKeys = getPrimaryKeyFields( modelClass );
// TODO: write generation support for multi-primary-key support.
// would likely need to write a java class that can supply an ObjectIdentity
// to the jpox/jdo implementation.
if ( primaryKeys.size() > 1 )
{
throw new ModelloException(
"The JDO mapping generator does not yet support Object Identifier generation " + "for the "
+ primaryKeys.size() + " fields specified as <identifier> or "
+ "with jpox.primary-key=\"true\""
);
}
if ( primaryKeys.size() == 1 )
{
ModelField modelField = (ModelField) primaryKeys.get( 0 );
String objectIdClass = (String) PRIMITIVE_IDENTITY_MAP.get( modelField.getType() );
if ( StringUtils.isNotEmpty( objectIdClass ) )
{
writer.addAttribute( "objectid-class", objectIdClass );
}
}
}
if ( needInheritance )
{
writer.startElement( "inheritance" );
// TODO: The table strategy should be customizable
// http://www.jpox.org/docs/1_1/inheritance.html - in particular
// the strategy="subclass-table" and strategy="new-table" parts
writer.addAttribute( "strategy", "new-table" );
writer.endElement();
}
// ----------------------------------------------------------------------
// Write all fields
// ----------------------------------------------------------------------
for ( Iterator it = fields.iterator(); it.hasNext(); )
{
ModelField modelField = (ModelField) it.next();
writeModelField( writer, modelField );
}
// Write ignored fields.
List ignoredFields = jpoxMetadata.getNotPersisted();
if ( ignoredFields != null )
{
Iterator it = ignoredFields.iterator();
while ( it.hasNext() )
{
String fieldName = (String) it.next();
writer.startElement( "field" );
writer.addAttribute( "name", fieldName );
writer.addAttribute( "persistence-modifier", "none" );
writer.endElement();
}
}
// ----------------------------------------------------------------------
// Write out the "detailed" fetch group. This group will by default
// contain all fields in a object. The default fetch group will contain
// all the primitives in a class as by JDO defaults.
// ----------------------------------------------------------------------
List detailedFields = new ArrayList();
for ( Iterator it = fields.iterator(); it.hasNext(); )
{
ModelField field = (ModelField) it.next();
if ( field.isPrimitive() )
{
continue;
}
if ( field instanceof ModelAssociation )
{
StoreAssociationMetadata storeMetadata = getAssociationMetadata( (ModelAssociation) field );
if ( storeMetadata.isPart() != null && storeMetadata.isPart().booleanValue() )
{
continue;
}
}
detailedFields.add( field );
}
// ----------------------------------------------------------------------
// Write all fetch groups
// ----------------------------------------------------------------------
// Write defaut detail fetch group
writeFetchGroup( writer, modelClass.getName() + "_detail", detailedFields, true );
// Write user fetch groups
Map fetchsMap = new HashMap();
for ( Iterator it = fields.iterator(); it.hasNext(); )
{
ModelField field = (ModelField) it.next();
JPoxFieldMetadata jpoxFieldMetadata = (JPoxFieldMetadata) field.getMetadata( JPoxFieldMetadata.ID );
List names = jpoxFieldMetadata.getFetchGroupNames();
if ( names != null )
{
for ( Iterator i = names.iterator(); i.hasNext(); )
{
String fetchGroupName = (String) i.next();
List fetchList;
if ( fetchsMap.get( fetchGroupName ) == null )
{
fetchList = new ArrayList();
}
else
{
fetchList = (List) fetchsMap.get( fetchGroupName );
}
fetchList.add( field );
fetchsMap.put( fetchGroupName, fetchList );
}
}
}
for ( Iterator it = fetchsMap.keySet().iterator(); it.hasNext(); )
{
String fetchName = (String) it.next();
writeFetchGroup( writer, fetchName, (List) fetchsMap.get( fetchName ), false );
}
writer.endElement(); // class
}
/**
* Utility method to obtain the table name from the {@link ModelClass} or {@link JPoxClassMetadata} taking into
* account the possibility of prefixes in use at the {@link JPoxModelMetadata} level.
*
* @param modelClass the model class to get the class.name from.
* @param classMetadata the class metadata to get the class-metadata.table name from.
* @return the table name (with possible prefix applied)
* @throws ModelloException if there was a problem with the table name violating a sql reserved word.
*/
private String getTableName( ModelClass modelClass, JPoxClassMetadata classMetadata )
throws ModelloException
{
JPoxModelMetadata modelMetadata = (JPoxModelMetadata) modelClass.getModel().getMetadata( JPoxModelMetadata.ID );
boolean hasPrefix = StringUtils.isNotEmpty( modelMetadata.getTablePrefix() );
boolean hasAlternateName = StringUtils.isNotEmpty( classMetadata.getTable() );
String prefix = "";
if ( hasPrefix )
{
prefix = modelMetadata.getTablePrefix().trim();
}
String tableName = null;
if ( hasAlternateName )
{
tableName = prefix + classMetadata.getTable();
}
else
{
tableName = prefix + modelClass.getName();
}
if ( sqlReservedWords.isKeyword( tableName ) )
{
StringBuffer emsg = new StringBuffer();
/* ----------------------------------------------------------------
* SQL Reserved Word Violation: 'ROLES'
* Context: TABLE NAME
*/
emsg.append( EOL ).append( ERROR_LINE ).append( EOL );
emsg.append( " SQL Reserved Word Violation: " ).append( tableName ).append( EOL );
emsg.append( " Context: TABLE NAME" ).append( EOL );
emsg.append( " " ).append( EOL );
/* In Model:
* <model jpox.table-prefix="">
* <class jpox.table="">
* <name>JdoRole</name>
* </class>
* </model>
*/
emsg.append( " In Model:" ).append( EOL );
emsg.append( " <model" );
if ( hasPrefix )
{
emsg.append( " jpox.table-prefix=\"" ).append( modelMetadata.getTablePrefix() ).append( "\"" );
}
emsg.append( ">" ).append( EOL );
emsg.append( " <class" );
if ( hasAlternateName )
{
emsg.append( " jpox.table=\"" ).append( classMetadata.getTable() ).append( "\"" );
}
emsg.append( ">" ).append( EOL );
emsg.append( " <name>" ).append( modelClass.getName() ).append( "</name>" ).append( EOL );
emsg.append( " </class>" ).append( EOL );
emsg.append( " </model>" ).append( EOL );
emsg.append( " " ).append( EOL );
/* Violation Source(s): Oracle (WARNING)
* SQL 99 (ERROR)
*
* Severity: ERROR - You must change this name for maximum
* compatibility amoungst JDBC SQL Servers.
*
* Severity: WARNING - You are encouraged to change this name
* for maximum compatibility amoungst JDBC SQL Servers.
*/
boolean hasError = appendKeywordSourceViolations( tableName, emsg );
/* Suggestions: 1) Use a different prefix in
* <model jpox.table-prefix="DIFFERENT_">
* 2) Use a different alternate table name using
* <class jpox.table="DIFFERENT">
* 3) Use a different class name in
* <class>
* <name>DIFFERENT</name>
* </class>
* ----------------------------------------------------------------
*/
emsg.append( " Suggestions: 1) Use a different prefix in" ).append( EOL );
emsg.append( " <model jpox.table-prefix=\"DIFFERENT_\">" ).append( EOL );
emsg.append( " 2) Use a different alternate table name using" ).append( EOL );
emsg.append( " <class jpox.table=\"DIFFERENT\">" ).append( EOL );
emsg.append( " 3) Use a different class name in" ).append( EOL );
emsg.append( " <class>" ).append( EOL );
emsg.append( " <name>DIFFERENT</name>" ).append( EOL );
emsg.append( " </class>" ).append( EOL );
emsg.append( ERROR_LINE );
// Determine possible exception.
if ( hasError || modelMetadata.getReservedWordStrictness().equals( JPoxModelMetadata.WARNING ) )
{
throw new ModelloException( emsg.toString() );
}
// No exception. use it. But log it.
getLogger().warn( emsg.toString() );
}
return tableName;
}
/**
* Support method for the {@link #getTableName(ModelClass, JPoxClassMetadata)},
* {@link #getColumnName(ModelField, JPoxFieldMetadata)}, and
* {@link #getJoinTableName(ModelField, JPoxFieldMetadata)} reserved word tests.
*
* @param word the word in violation.
* @param emsg the string buffer to append to.
* @return if this word has any ERROR severity level violations.
*/
private boolean appendKeywordSourceViolations( String word, StringBuffer emsg )
{
List sources = sqlReservedWords.getKeywordSourceList( word );
boolean hasError = false;
emsg.append( " Violation Source(s): " );
for ( Iterator it = sources.iterator(); it.hasNext(); )
{
KeywordSource source = (KeywordSource) it.next();
emsg.append( source.getName() ).append( " (" ).append( source.getSeverity() ).append( ")" );
emsg.append( EOL );
if ( source.getSeverity().equalsIgnoreCase( "ERROR" ) )
{
hasError = true;
}
if ( it.hasNext() )
{
emsg.append( " " );
}
}
emsg.append( " " ).append( EOL );
emsg.append( " Severity: " );
if ( hasError )
{
emsg.append( "ERROR - You must change this name for the maximum" ).append( EOL );
emsg.append( " compatibility amoungst JDBC SQL Servers." ).append( EOL );
}
else
{
emsg.append( "WARNING - You are encouraged to change this name" ).append( EOL );
emsg.append( " for maximum compatibility amoungst JDBC SQL Servers." ).append( EOL );
}
emsg.append( " " ).append( EOL );
return hasError;
}
private void writeModelloMetadataClass( XMLWriter writer )
throws ModelloException
{
writer.startElement( "class" );
writer.addAttribute( "name", getModel().getName() + "ModelloMetadata" );
writer.addAttribute( "detachable", String.valueOf( true ) );
writer.startElement( "field" );
writer.addAttribute( "name", "modelVersion" );
writer.addAttribute( "null-value", "default" );
writer.startElement( "column" );
writer.addAttribute( "default-value", getGeneratedVersion().toString() );
writer.endElement(); // column
writer.endElement(); // field
writer.endElement(); // class
}
private void writeFetchGroup( XMLWriter writer, String fetchGroupName, List fields, boolean onlyIfIsStashPart )
{
if ( !fields.isEmpty() )
{
writer.startElement( "fetch-group" );
writer.addAttribute( "name", fetchGroupName );
for ( Iterator it = fields.iterator(); it.hasNext(); )
{
ModelField field = (ModelField) it.next();
if ( onlyIfIsStashPart && ( field instanceof ModelAssociation ) )
{
StoreAssociationMetadata storeMetadata = getAssociationMetadata( (ModelAssociation) field );
if ( storeMetadata.isPart() != null && storeMetadata.isPart().booleanValue() )
{
continue;
}
}
writer.startElement( "field" );
writer.addAttribute( "name", field.getName() );
writer.endElement();
}
writer.endElement(); // fetch-group
}
}
private void writeModelField( XMLWriter writer, ModelField modelField )
throws ModelloException
{
writer.startElement( "field" );
StoreFieldMetadata storeMetadata = (StoreFieldMetadata) modelField.getMetadata( StoreFieldMetadata.ID );
JPoxFieldMetadata jpoxMetadata = (JPoxFieldMetadata) modelField.getMetadata( JPoxFieldMetadata.ID );
writer.addAttribute( "name", modelField.getName() );
if ( !storeMetadata.isStorable() )
{
writer.addAttribute( "persistence-modifier", "none" );
}
else if ( StringUtils.isNotEmpty( jpoxMetadata.getPersistenceModifier() ) )
{
writer.addAttribute( "persistence-modifier", jpoxMetadata.getPersistenceModifier() );
}
if ( modelField.isRequired() )
{
writer.addAttribute( "null-value", "exception" );
}
else if ( jpoxMetadata.getNullValue() != null )
{
writer.addAttribute( "null-value", jpoxMetadata.getNullValue() );
}
String columnName = getColumnName( modelField, jpoxMetadata );
if ( StringUtils.isNotEmpty( jpoxMetadata.getJoinTableName() ) )
{
writer.addAttribute( "table", getJoinTableName( modelField, jpoxMetadata ) );
}
if ( jpoxMetadata.isPrimaryKey() )
{
writer.addAttribute( "primary-key", "true" );
// value-strategy is only useful when you have a primary-key defined for the field.
if ( StringUtils.isNotEmpty( valueStrategyOverride ) )
{
writeValueStrategy( valueStrategyOverride, writer );
}
else if ( StringUtils.isNotEmpty( jpoxMetadata.getValueStrategy() ) )
{
writeValueStrategy( jpoxMetadata.getValueStrategy(), writer );
}
}
if ( StringUtils.isNotEmpty( jpoxMetadata.getIndexed() ) )
{
writer.addAttribute( "indexed", jpoxMetadata.getIndexed() );
}
if ( StringUtils.isNotEmpty( jpoxMetadata.getMappedBy() ) )
{
writer.addAttribute( "mapped-by", jpoxMetadata.getMappedBy() );
}
if ( modelField instanceof ModelAssociation )
{
writeAssociation( writer, (ModelAssociation) modelField );
}
else
{
if ( modelField.isPrimitiveArray() )
{
writer.startElement( "array" );
writer.endElement();
}
// Work out potential <column> subelement
// Store potential column properties.
Properties columnProps = new Properties();
if ( !StringUtils.equalsIgnoreCase( columnName, modelField.getName() ) )
{
columnProps.setProperty( "name", columnName );
}
if ( storeMetadata.getMaxSize() > 0 )
{
columnProps.setProperty( "length", String.valueOf( storeMetadata.getMaxSize() ) );
}
if ( StringUtils.equals( jpoxMetadata.getNullValue(), "default" ) )
{
columnProps.setProperty( "default-value", modelField.getDefaultValue() );
}
// Now write the column sub element (if it has properties)
if ( !columnProps.isEmpty() )
{
writer.startElement( "column" );
for ( Enumeration en = columnProps.propertyNames(); en.hasMoreElements(); )
{
String attributeName = (String) en.nextElement();
String attributeValue = columnProps.getProperty( attributeName );
writer.addAttribute( attributeName, attributeValue );
}
writer.endElement();
}
// Work out potential <unique> subelement.
if ( jpoxMetadata.isUnique() )
{
writer.startElement( "unique" );
writer.addAttribute( "name", columnName.toUpperCase() + "_UNIQUE_CONSTRAINT" );
writer.endElement();
}
// Work out potential <foreign-key> subelement
if ( jpoxMetadata.isForeignKey() )
{
writer.startElement( "foreign-key" );
writer.addAttribute( "name", columnName.toUpperCase() + "_FK" );
if ( StringUtils.isNotEmpty( jpoxMetadata.getForeignKeyDeferred() ) )
{
writer.addAttribute( "deferred", jpoxMetadata.getForeignKeyDeferred() );
}
if ( StringUtils.isNotEmpty( jpoxMetadata.getForeignKeyDeleteAction() ) )
{
writer.addAttribute( "delete-action", jpoxMetadata.getForeignKeyDeleteAction() );
}
if ( StringUtils.isNotEmpty( jpoxMetadata.getForeignKeyUpdateAction() ) )
{
writer.addAttribute( "update-action", jpoxMetadata.getForeignKeyUpdateAction() );
}
writer.endElement();
}
}
writer.endElement(); // field
}
/**
* Utility method to obtain the join table name from the {@link JPoxFieldMetadata} taking into
* account the possibility of prefixes in use at the {@link JPoxModelMetadata} level.
*
* @param modelField the model field to get the field.name from.
* @param fieldMetadata the field metadata to get the field-metadata.join-table name from.
* @return the join table name (with possible prefix applied)
* @throws ModelloException if there was a problem with the table name violating a sql reserved word.
*/
private String getJoinTableName( ModelField modelField, JPoxFieldMetadata fieldMetadata )
throws ModelloException
{
ModelClass modelClass = modelField.getModelClass();
JPoxModelMetadata modelMetadata = (JPoxModelMetadata) modelClass.getModel().getMetadata( JPoxModelMetadata.ID );
boolean hasPrefix = StringUtils.isNotEmpty( modelMetadata.getTablePrefix() );
String prefix = "";
if ( hasPrefix )
{
prefix = modelMetadata.getTablePrefix().trim();
}
String joinTableName = prefix + fieldMetadata.getJoinTableName();
;
if ( sqlReservedWords.isKeyword( joinTableName ) )
{
StringBuffer emsg = new StringBuffer();
/* ----------------------------------------------------------------
* SQL Reserved Word Violation: 'ROLES'
* Context: TABLE NAME
*/
emsg.append( EOL ).append( ERROR_LINE ).append( EOL );
emsg.append( " SQL Reserved Word Violation: " ).append( joinTableName ).append( EOL );
emsg.append( " Context: JOIN TABLE NAME" ).append( EOL );
emsg.append( " " ).append( EOL );
/* In Model:
* <model jpox.table-prefix="">
* <class jpox.table="">
* <name>JdoRole</name>
* <fields>
* <field jpox.join-table="Foo">
* <name>Operation</name>
* </field>
* </fields>
* </class>
* </model>
*/
emsg.append( " In Model:" ).append( EOL );
emsg.append( " <model" );
if ( hasPrefix )
{
emsg.append( " jpox.table-prefix=\"" ).append( modelMetadata.getTablePrefix() ).append( "\"" );
}
emsg.append( ">" ).append( EOL );
emsg.append( " <class>" ).append( EOL );
emsg.append( " <name>" ).append( modelClass.getName() ).append( "</name>" ).append( EOL );
emsg.append( " <fields>" ).append( EOL );
emsg.append( " <field jpox.join-table=\"" ).append( fieldMetadata.getJoinTableName() );
emsg.append( "\">" ).append( EOL );
emsg.append( " <name>" ).append( modelField.getName() ).append( "</name>" ).append( EOL );
emsg.append( " <field>" ).append( EOL );
emsg.append( " </fields>" ).append( EOL );
emsg.append( " </class>" ).append( EOL );
emsg.append( " </model>" ).append( EOL );
emsg.append( " " ).append( EOL );
/* Violation Source(s): Oracle (WARNING)
* SQL 99 (ERROR)
*
* Severity: ERROR - You must change this name for maximum
* compatibility amoungst JDBC SQL Servers.
*
* Severity: WARNING - You are encouraged to change this name
* for maximum compatibility amoungst JDBC SQL Servers.
*/
boolean hasError = appendKeywordSourceViolations( joinTableName, emsg );
/* Suggestions: 1) Use a different table prefix in
* <model jpox.table-prefix="DIFFERENT_">
* 2) Use a different join table name using
* <field jpox.join-table="DIFFERENT">
* ----------------------------------------------------------------
*/
emsg.append( " Suggestions: 1) Use a different table prefix in" ).append( EOL );
emsg.append( " <model jpox.table-prefix=\"DIFFERENT_\">" ).append( EOL );
emsg.append( " 2) Use a different join table name using" ).append( EOL );
emsg.append( " <field jpox.join-table=\"DIFFERENT\">" ).append( EOL );
emsg.append( ERROR_LINE );
// Determine possible exception.
if ( hasError || modelMetadata.getReservedWordStrictness().equals( JPoxModelMetadata.WARNING ) )
{
throw new ModelloException( emsg.toString() );
}
// No exception. use it. But log it.
getLogger().warn( emsg.toString() );
}
return joinTableName;
}
/**
* Utility method to obtain the column name from the {@link ModelField} or {@link JPoxFieldMetadata} taking into
* account the possibility of prefixes in use at the {@link JPoxModelMetadata} or {@link JPoxClassMetadata} level.
*
* @param modelField the model field to get the field.name from.
* @param fieldMetadata the field metadata to get the field-metadata.column name from.
* @return the column name (with possible prefix applied)
* @throws ModelloException if there was a problem with the column name violating a sql reserved word.
*/
private String getColumnName( ModelField modelField, JPoxFieldMetadata fieldMetadata )
throws ModelloException
{
boolean hasClassPrefix = false;
boolean hasModelPrefix = false;
boolean hasAlternateName = false;
ModelClass modelClass = modelField.getModelClass();
JPoxClassMetadata classMetadata = (JPoxClassMetadata) modelClass.getMetadata( JPoxClassMetadata.ID );
JPoxModelMetadata modelMetadata = (JPoxModelMetadata) modelClass.getModel().getMetadata( JPoxModelMetadata.ID );
String prefix = "";
if ( StringUtils.isNotEmpty( modelMetadata.getColumnPrefix() ) )
{
prefix = modelMetadata.getColumnPrefix().trim();
hasModelPrefix = true;
}
if ( StringUtils.isNotEmpty( classMetadata.getColumnPrefix() ) )
{
prefix = classMetadata.getColumnPrefix();
hasClassPrefix = true;
}
String columnName = "";
if ( StringUtils.isNotEmpty( fieldMetadata.getColumnName() ) )
{
columnName = prefix + fieldMetadata.getColumnName();
hasAlternateName = true;
}
else
{
columnName = prefix + modelField.getName();
}
if ( sqlReservedWords.isKeyword( columnName ) )
{
StringBuffer emsg = new StringBuffer();
/* ----------------------------------------------------------------
* SQL Reserved Word Violation: 'ROLES'
* Context: TABLE NAME
*/
emsg.append( EOL ).append( ERROR_LINE ).append( EOL );
emsg.append( " SQL Reserved Word Violation: " ).append( columnName ).append( EOL );
emsg.append( " Context: COLUMN NAME" ).append( EOL );
emsg.append( " " ).append( EOL );
/* In Model:
* <model jpox.column-prefix="">
* <class jpox.column-prefix="">
* <name>JdoRole</name>
* <fields>
* <field jpox.column="">
* <name>operation</name>
* </field>
* </fields>
* </class>
* </model>
*/
emsg.append( " In Model:" ).append( EOL );
emsg.append( " <model" );
if ( hasModelPrefix )
{
emsg.append( " jpox.column-prefix=\"" ).append( modelMetadata.getColumnPrefix() ).append( "\"" );
}
emsg.append( ">" ).append( EOL );
emsg.append( " <class" );
if ( hasClassPrefix )
{
emsg.append( " jpox.column-prefix=\"" ).append( classMetadata.getColumnPrefix() ).append( "\"" );
}
emsg.append( ">" ).append( EOL );
emsg.append( " <name>" ).append( modelClass.getName() ).append( "</name>" ).append( EOL );
emsg.append( " <fields>" ).append( EOL );
emsg.append( " <field" );
if ( hasAlternateName )
{
emsg.append( " jpox.column=\"" ).append( fieldMetadata.getColumnName() ).append( "\"" );
}
emsg.append( ">" ).append( EOL );
emsg.append( " <name>" ).append( modelField.getName() ).append( "</name>" ).append( EOL );
emsg.append( " <field>" ).append( EOL );
emsg.append( " </fields>" ).append( EOL );
emsg.append( " </class>" ).append( EOL );
emsg.append( " </model>" ).append( EOL );
emsg.append( " " ).append( EOL );
/* Violation Source(s): Oracle (WARNING)
* SQL 99 (ERROR)
*
* Severity: ERROR - You must change this name for maximum
* compatibility amoungst JDBC SQL Servers.
*
* Severity: WARNING - You are encouraged to change this name
* for maximum compatibility amoungst JDBC SQL Servers.
*/
boolean hasError = appendKeywordSourceViolations( columnName, emsg );
/* Suggestions: 1) Use a different model column prefix in
* <model jpox.column-prefix="DIFFERENT_">
* 2) Use a different class column prefix in
* <class jpox.column-prefix="DIFFERENT_">
* 3) Use a different alternate column name using
* <field jpox.column="DIFFERENT">
* 4) Use a different field name in
* <class>
* <name>Foo</name>
* <fields>
* <field>
* <name>DIFFERENT</name>
* </field>
* </fields>
* </class>
* ----------------------------------------------------------------
*/
emsg.append( " Suggestions: 1) Use a different model column prefix in" ).append( EOL );
emsg.append( " <model jpox.column-prefix=\"DIFFERENT_\">" ).append( EOL );
emsg.append( " 2) Use a different class column prefix in" ).append( EOL );
emsg.append( " <class jpox.column-prefix=\"DIFFERENT_\">" ).append( EOL );
emsg.append( " 3) Use a different alternate column name using" ).append( EOL );
emsg.append( " <field jpox.column=\"DIFFERENT\">" ).append( EOL );
emsg.append( " 4) Use a different field name in" ).append( EOL );
emsg.append( " <class>" ).append( EOL );
emsg.append( " <name>" ).append( modelClass.getName() ).append( "</name>" ).append(
EOL );
emsg.append( " <fields>" ).append( EOL );
emsg.append( " <field>" ).append( EOL );
emsg.append( " <name>DIFFERENT</name>" ).append( EOL );
emsg.append( " <field>" ).append( EOL );
emsg.append( " </fields>" ).append( EOL );
emsg.append( " </class>" ).append( EOL );
emsg.append( ERROR_LINE );
// Determine possible exception.
if ( hasError || modelMetadata.getReservedWordStrictness().equals( JPoxModelMetadata.WARNING ) )
{
throw new ModelloException( emsg.toString() );
}
// No exception. use it. But log it.
getLogger().warn( emsg.toString() );
}
return columnName;
}
private static void writeValueStrategy( String valueStrategy, XMLWriter writer )
throws ModelloException
{
if ( !"off".equals( valueStrategy ) )
{
if ( !VALUE_STRATEGY_LIST.contains( valueStrategy ) )
{
throw new ModelloException(
"The JDO mapping generator does not support the specified " + "value-strategy '" + valueStrategy
+ "'. " + "Supported types: " + VALUE_STRATEGY_LIST
);
}
writer.addAttribute( "value-strategy", valueStrategy );
}
}
private void writeAssociation( XMLWriter writer, ModelAssociation association )
{
StoreAssociationMetadata am =
(StoreAssociationMetadata) association.getAssociationMetadata( StoreAssociationMetadata.ID );
JPoxAssociationMetadata jpoxMetadata =
(JPoxAssociationMetadata) association.getAssociationMetadata( JPoxAssociationMetadata.ID );
if ( am.isPart() != null )
{
// This gets added onto the <field> element
writer.addAttribute( "default-fetch-group", am.isPart().toString() );
}
if ( association.getType().equals( "java.util.List" ) || association.getType().equals( "java.util.Set" ) )
{
// Start <collection> element
writer.startElement( "collection" );
if ( association.getTo().equals( "String" ) )
{
writer.addAttribute( "element-type", "java.lang.String" );
}
else
{
writer.addAttribute( "element-type", association.getTo() );
}
if ( jpoxMetadata.isDependent() )
{
writer.addAttribute( "dependent-element", "true" );
}
else
{
writer.addAttribute( "dependent-element", "false" );
}
// End <collection> element
writer.endElement();
if ( jpoxMetadata.isJoin() )
{
writer.startElement( "join" );
writer.endElement();
}
}
else if ( association.getType().equals( "java.util.Map" ) )
{
writer.startElement( "map" );
writer.addAttribute( "key-type", am.getKeyType() );
if ( association.getTo().equals( "String" ) )
{
writer.addAttribute( "value-type", "java.lang.String" );
}
else
{
writer.addAttribute( "value-type", association.getTo() );
}
if ( jpoxMetadata.isDependent() )
{
writer.addAttribute( "dependent-key", "true" );
writer.addAttribute( "dependent-value", "true" );
}
else
{
writer.addAttribute( "dependent-key", "false" );
writer.addAttribute( "dependent-value", "false" );
}
writer.endElement();
if ( jpoxMetadata.isJoin() )
{
writer.startElement( "join" );
writer.endElement();
}
}
else if ( association.getType().equals( "java.util.Properties" ) )
{
writer.addAttribute( "embedded", "false" );
writer.startElement( "map" );
writer.addAttribute( "key-type", "java.lang.String" );
writer.addAttribute( "value-type", "java.lang.String" );
writer.addAttribute( "embedded-key", "true" );
writer.addAttribute( "embedded-value", "true" );
writer.addAttribute( "dependent-key", "true" );
writer.addAttribute( "dependent-value", "true" );
writer.endElement();
if ( jpoxMetadata.isJoin() )
{
writer.startElement( "join" );
writer.endElement();
}
}
else
// One association
{
if ( jpoxMetadata.isDependent() )
{
writer.addAttribute( "dependent", "true" );
}
}
}
private boolean isInstantionApplicationType( ModelClass modelClass )
{
List identifierFields = modelClass.getIdentifierFields( getGeneratedVersion() );
return identifierFields.size() > 0;
}
private List getPrimaryKeyFields( ModelClass modelClass )
throws ModelloException
{
List primaryKeys = new ArrayList();
List fields = modelClass.getFields( getGeneratedVersion() );
JPoxClassMetadata jpoxClassMetadata = (JPoxClassMetadata) modelClass.getMetadata( JPoxClassMetadata.ID );
for ( Iterator it = fields.iterator(); it.hasNext(); )
{
ModelField modelField = (ModelField) it.next();
JPoxFieldMetadata jpoxFieldMetadata = (JPoxFieldMetadata) modelField.getMetadata( JPoxFieldMetadata.ID );
if ( jpoxClassMetadata.useIdentifiersAsPrimaryKey() )
{
if ( modelField.isIdentifier() )
{
assertSupportedIdentityPrimitive( modelField );
primaryKeys.add( modelField );
}
}
else
{
if ( jpoxFieldMetadata.isPrimaryKey() )
{
assertSupportedIdentityPrimitive( modelField );
primaryKeys.add( modelField );
}
}
}
return primaryKeys;
}
private void assertSupportedIdentityPrimitive( ModelField modelField )
throws ModelloException
{
if ( !PRIMITIVE_IDENTITY_MAP.containsKey( modelField.getType() ) )
{
throw new ModelloException(
"The JDO mapping generator does not support the specified " + "field type '" + modelField.getType()
+ "'. " + "Supported types: " + PRIMITIVE_IDENTITY_MAP.keySet()
);
}
}
private StoreAssociationMetadata getAssociationMetadata( ModelAssociation association )
{
return (StoreAssociationMetadata) association.getAssociationMetadata( StoreAssociationMetadata.ID );
}
}