/**
* Copyright (C) 2001-2004 France Telecom R&D
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.objectweb.speedo.generation.mivisitor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.objectweb.speedo.api.SpeedoException;
import org.objectweb.speedo.api.SpeedoProperties;
import org.objectweb.speedo.generation.lib.AbstractGeneratorComponent;
import org.objectweb.speedo.lib.Personality;
import org.objectweb.speedo.metadata.SpeedoClass;
import org.objectweb.speedo.metadata.SpeedoCollection;
import org.objectweb.speedo.metadata.SpeedoColumn;
import org.objectweb.speedo.metadata.SpeedoCommonField;
import org.objectweb.speedo.metadata.SpeedoDiscriminator;
import org.objectweb.speedo.metadata.SpeedoField;
import org.objectweb.speedo.metadata.SpeedoInheritance;
import org.objectweb.speedo.metadata.SpeedoInheritedField;
import org.objectweb.speedo.metadata.SpeedoJoin;
import org.objectweb.speedo.metadata.SpeedoJoinColumn;
import org.objectweb.speedo.metadata.SpeedoMap;
import org.objectweb.speedo.metadata.SpeedoPackage;
import org.objectweb.speedo.metadata.SpeedoTable;
import org.objectweb.speedo.metadata.SpeedoXMLDescriptor;
import org.objectweb.speedo.naming.api.NamingManager;
import org.objectweb.util.monolog.api.BasicLevel;
/**
* This Speedo Meta information visitor builds and fills the Speedo Meta
* information concerning the OR mapping information. It defines a SpeedoTable,
* SpeedoColumn, SpeedoJoin where it is required.
* This SMI visitor cannot be inclued with other SMI visitor. Indeed it requires
* that all meta information is complete (for all classes). In particular it
* needs the result of the Class analyser visitor. For this reason this SMI
* visitor is a simple GeneratorComponent to use when SMI is complete.
* In addition, due to dependency between classes (relation ship, inheritance)
* the visit of classes must be done in a particular order. For these reasons,
* this Visitor visits the SMI with a help of two internal classes (
* VisitRequired and VisitRemeber).
*
* @author S.Chassande-Barrioz
*/
public class ORMappingGenerator extends AbstractGeneratorComponent {
/**
* This class represents required element to be visited in a persistent
* class.
*
* @author S.Chassande-Barrioz
*/
public static class VisitRequired {
/**
* constant representing a visit of nothing
*/
public final static VisitRequired NOTHING = new VisitRequired(false, false, null);
/**
* constant representing a visit of all elements of a persistent class
*/
public final static VisitRequired ALL = new VisitRequired(true, true, null);
/**
* constant representing a visit of base element of a persistent class
*/
public final static VisitRequired BASE = new VisitRequired(true, false, null);
/**
* indicates if the base must be visited. The base represents:
* - the table of the persistent class
* - the identifier of the persistent class
* - the primitivie fields of the persistent class
*/
public boolean base = false;
/**
* Indicates if all references must be visisted (ClassRef
* and GenClassRef)
*/
public boolean references = false;
/**
* indicates if the visit of a particular reference field is required.
* It is often used in case of bi directional relationship with one side
* of the relation mmped by the other one.
*/
public SpeedoField refField = null;
/**
* Builds a new VisitRequired instance including the base elements and
* a particular reference field.
* @param refField the field to visit
*/
public VisitRequired(SpeedoField refField) {
this.base = true;
this.references = false;
this.refField = refField;
}
/**
* Private constructor for static constant only.
*/
private VisitRequired(boolean base, boolean references, SpeedoField refField) {
this.base = base;
this.references = references;
this.refField = refField;
}
/**
* Prints a string representing the current instance.
*/
public String toString() {
if (base) {
if (references) {
return "ALL";
} else if (refField == null) {
return "BASE";
} else {
return "BASE, " + refField.name;
}
} else {
return "NOTHING";
}
}
}
/**
* This class represents the status of visited/treated elements of a
* persistent class.
*
* @author S.Chassande-Barrioz
*/
public static class VisitRemember {
/**
* The persistent class
*/
private SpeedoClass sc;
/**
* indicates if the base elements have been visited:
* - the table of the persistent class
* - the identifier of the persistent class
* - the primitivie fields of the persistent class
*/
private boolean baseVisited = false;
/**
* Indicates if inherited fields have been visited.
*/
private boolean inheritedFieldsVisited = false;
/**
* the list primitive fields of the persistent class
* The content of the list is SpeedoField instances.
*/
public List primitiveFields = new ArrayList();
/**
* the list reference fields of the persistent class (class reference
* and generic class reference)
* The content of the list is SpeedoField instances.
*/
public List references = new ArrayList();
/**
* The list of reference fields already visisted.
*/
private List visitedReferencesFields = new ArrayList();
/**
* Builds a new instance for a persistent class. This constructor fills
* the list of persistent fields.
*/
public VisitRemember(SpeedoClass c) {
sc = c;
for (Iterator fieldIt = sc.fields.values().iterator(); fieldIt.hasNext();) {
SpeedoField sf = (SpeedoField) fieldIt.next();
if (sf.jdoTuple != null) {
references.add(sf);
} else {
SpeedoClass rclass = sf.getReferencedClass();
if (rclass == null) {
primitiveFields.add(sf);
} else {
references.add(sf);
}
}
}
}
/**
* Print the status of the class visit
*/
public String toString() {
if (baseVisited) {
int nbref = references.size();
int visitedref = visitedReferencesFields.size();
if (nbref == visitedref) {
return "ALL";
} else if (visitedref == 0) {
return "BASE";
} else {
StringBuffer sb = new StringBuffer();
sb.append("BASE");
for (Iterator it = visitedReferencesFields.iterator(); it.hasNext();) {
SpeedoField sf = (SpeedoField) it.next();
sb.append(", ").append(sf.name);
}
return sb.toString();
}
} else {
return "NOTHING";
}
}
/**
* Indicates if some elements specified by the VisitRequired parameter
* have not been already visited.
*/
public boolean hasUnvisitedPart(VisitRequired req) {
if (req.base && !baseVisited) {
return true;
}
if (req.references
&& references.size() > visitedReferencesFields.size()) {
return true;
}
if (req.refField != null
&& !visitedReferencesFields.contains(req.refField)) {
return true;
}
if (req.references && !inheritedFieldsVisited) {
return true;
}
return false;
}
/**
* Indicates if the base must be visited according to the parameter
* and the current status.
*/
public boolean visitBase(VisitRequired vr) {
return vr.base && !baseVisited;
}
/**
* Callback method to indicate that the base elements have been visited.
*/
public void baseVisited() {
baseVisited = true;
}
/**
* Indicates if one or several reference fields must be visited
* according to the parameter and the current status.
*/
public boolean visitReferences(VisitRequired vr) {
return (vr.references || vr.refField != null)
&& references.size() > visitedReferencesFields.size();
}
/**
* Callback method to indicate that a reference field has been visited.
*/
public boolean visitReferenceField(SpeedoField sf, VisitRequired vr) {
return (vr.references || vr.refField == sf)
&& !visitedReferencesFields.contains(sf);
}
/**
* Callback method to indicate that a reference field has been visited.
*/
public void referenceFieldVisisted(SpeedoField sf) {
visitedReferencesFields.add(sf);
}
/**
* Indicates if inherited fields must be visited
* according to the parameter and the current status.
*/
public boolean visitInheritedFields(VisitRequired vr) {
return vr.references && !inheritedFieldsVisited;
}
/**
* Callback method to indicate that inherited fields have been visited.
*/
public void inheritedFieldsVisited() {
inheritedFieldsVisited = true;
}
}
/**
* is the map of visited classes (partialy of totaly)
* key = fully qualified persistent class name
* value = VisitRemener instance
*/
private HashMap visitedClasses = new HashMap();
public final static String LOGGER_NAME =
SpeedoProperties.LOGGER_NAME + ".generation.orm";
public ORMappingGenerator(Personality p) {
super(p);
}
public String getTitle() {
return "Auto O/R Mapping...";
}
protected String getLoggerName() {
return LOGGER_NAME;
}
public boolean init() throws SpeedoException {
visitedClasses.clear();
logger = scp.loggerFactory.getLogger(getLoggerName());
debug = logger != null && logger.isLoggable(BasicLevel.DEBUG);
return !scp.getXmldescriptor().isEmpty();
}
public void process() throws SpeedoException {
visitedClasses.clear();
for (Iterator xmlIt = scp.smi.xmlDescriptors.values().iterator(); xmlIt.hasNext();) {
SpeedoXMLDescriptor xml = (SpeedoXMLDescriptor) xmlIt.next();
for (Iterator packIt = xml.packages.values().iterator(); packIt.hasNext();) {
SpeedoPackage sp = (SpeedoPackage) packIt.next();
for (Iterator classIt = sp.classes.values().iterator(); classIt.hasNext();) {
SpeedoClass sc = (SpeedoClass) classIt.next();
visitSpeedoClass(sc, VisitRequired.ALL);
}
}
}
visitedClasses.clear();
}
/**
* Visits a SpeedoClass.
* @param sc the class to visit
* @param toVisit
* @throws SpeedoException
*/
private void visitSpeedoClass(SpeedoClass sc, VisitRequired toVisit) throws SpeedoException {
logger.log(BasicLevel.DEBUG, "* visit class '" + sc.getFQName() + "', " + toVisit + ".");
VisitRemember vr = (VisitRemember) visitedClasses.get(sc);
if (vr == null) {
vr = new VisitRemember(sc);
visitedClasses.put(sc, vr);
} else if (!vr.hasUnvisitedPart(toVisit)){
logger.log(BasicLevel.DEBUG, "\t=> already visited: " + vr + ".");
return;
}
if (vr.visitBase(toVisit)) {
logger.log(BasicLevel.DEBUG, "\tvisit base.");
//visit parents first
visitClassParent(sc, toVisit);
//visit inheritance strategy
visitClassInheritanceStrategy(sc);
//visit table of the persistent class
visitClassTable(sc);
//visit identity column of the persistent class
visitIdentityColumn(sc);
//visit secondary tables (not the join associated)
if (sc.joinToExtTables != null) {
for (int i = 0; i < sc.joinToExtTables.length; i++) {
SpeedoJoin join = sc.joinToExtTables[i];
if (join.mainTable == null) {
join.mainTable = sc.mainTable;
}
if (join.extTable == null) {
join.extTable = new SpeedoTable();
allocateTableName(sc, join.extTable);
}
}
}
//visit primitive Fields first because it can contain the pk fields used
// for classRef or genClassRef
//visit primitive fields (including pk fields)
for (Iterator fieldIt = vr.primitiveFields.iterator(); fieldIt.hasNext();) {
SpeedoField sf = (SpeedoField) fieldIt.next();
visitPrimitiveField(sf);
}
if (sc.joinToExtTables != null) {
for (int i = 0; i < sc.joinToExtTables.length; i++) {
visitJoinOfSecondaryTable(sc.joinToExtTables[i], sc);
}
}
vr.baseVisited();
}
if (vr.visitReferences(toVisit)) {
logger.log(BasicLevel.DEBUG, "\tvisit reference fields:");
//visit references fields
for (Iterator fieldIt = vr.references.iterator(); fieldIt.hasNext();) {
SpeedoField sf = (SpeedoField) fieldIt.next();
if (vr.visitReferenceField(sf, toVisit)) {
if (sf.jdoTuple == null) {
visitClassRefField(sf);
} else {
visitGenClassRefField(sf);
}
vr.referenceFieldVisisted(sf);
}
}
}
if (vr.visitInheritedFields(toVisit)) {
visitClassInheritance(sc);
vr.inheritedFieldsVisited();
}
logger.log(BasicLevel.DEBUG, "End of visit class '"
+ sc.getFQName() + "', " + toVisit + ".");
}
private void visitJoinOfSecondaryTable(SpeedoJoin join, SpeedoClass sc) throws SpeedoException {
logger.log(BasicLevel.DEBUG, "\t\tvisit join to the secondary table '" + join.extTable.name + "'.");
if (join.columns.isEmpty()) {
//create new JoinColumn to the pk column of the class
join.columns.addAll(getFKJoinColumn(sc, "FK_", join.extTable));
} else {
int size = join.columns.size();
for (Iterator it = join.columns.iterator(); it.hasNext();) {
SpeedoJoinColumn jc = (SpeedoJoinColumn) it.next();
if (jc.targetColumn == null) {
if (jc.targetField == null) {
if (size == 1) {
SpeedoColumn[] cols = getIdColumns(sc);
if (cols.length > 1) {
throw new SpeedoException("The join to the '"
+ join.extTable.name
+ "' secondary table has not targetColumn defined and there is several identifier column in the "
+ sc.getSourceDesc());
}
jc.targetColumn = cols[0].name;
logger.log(BasicLevel.DEBUG, "\t\tset the target column from the unique PK column: " + jc.targetColumn);
} else {
throw new SpeedoException("The join columns to the '"
+ join.extTable.name
+ "' secondary table have not targetColumn defined in the "
+ sc.getSourceDesc());
}
} else {
//find the column of the target field
SpeedoField sf = sc.getField(jc.targetField);
if (sf == null) {
throw new SpeedoException("Targeted field '" + jc.targetField + "' in the " + sc.getSourceDesc() + ". It is defined in the join to the secondary table '" + join.extTable.name +"'.");
}
if (sf.columns == null || sf.columns.length != 1) {
throw new SpeedoException("In join column, target field must be a primitive field: " + sf.getSourceDesc() + ". It is defined in the join to the secondary table '" + join.extTable.name +"'.");
}
jc.targetColumn = sf.columns[0].name;
}
}
}
}
}
/**
* Visits parent persistent class starting with the root class.
* @param sc the Speedoclass to visit.
* @param toVisit
* @throws SpeedoException
*/
private void visitClassParent(SpeedoClass sc, VisitRequired toVisit) throws SpeedoException {
//find all not visited parents
List parents = new ArrayList();
SpeedoClass parent = sc.getSuper();
while (parent != null) {
VisitRemember vr = (VisitRemember) visitedClasses.get(parent);
if (vr == null || vr.hasUnvisitedPart(toVisit)) {
//Add in first
parents.add(0, parent);
parent = parent.getSuper();
} else {
//if a parent is visited all super parents are visited too
parent = null;
}
}
//visit parents
for (Iterator it = parents.iterator(); it.hasNext();) {
logger.log(BasicLevel.DEBUG, "\tvisit parent of " + sc.getFQName());
visitSpeedoClass((SpeedoClass) it.next(), toVisit);
}
}
/**
* Set inheritance strategy when it is not defined.
*/
private void visitClassInheritanceStrategy(SpeedoClass sc) throws SpeedoException {
//assign default inheritance strategy
if (sc.inheritance != null
&& sc.inheritance.superClassName != null
&& sc.inheritance.strategy == SpeedoInheritance.STRATEGY_UNKOWN) {
if (sc.mainTable == null) {
//no table defined
if (sc.inheritance.join == null) {
//no join ==> choose filtered
sc.inheritance.strategy = SpeedoInheritance.STRATEGY_SUPERCLASS_TABLE;
} else {
//there is a join ==> it means vertical
sc.inheritance.strategy = SpeedoInheritance.STRATEGY_NEW_TABLE;
}
} else {
// there is a table. It means horizontal or vertical
// either there is a join
sc.inheritance.strategy = SpeedoInheritance.STRATEGY_NEW_TABLE;
}
}
}
/**
* Computes the main table according to the inheritance strategy.
*/
private void visitClassTable(SpeedoClass sc) throws SpeedoException {
//check the mainTable
if (sc.mainTable == null) {
if (sc.inheritance == null || sc.inheritance.superClassName == null) {
sc.mainTable = new SpeedoTable();
} else if (sc.inheritance.isFilteredMapping()) {
//The table is one of the parent
sc.mainTable = getRootTable(sc.getSuper());
} else if (sc.inheritance.isHorizontalMapping()) {
sc.mainTable = new SpeedoTable();
} else if (sc.inheritance.isVerticalMapping()) {
SpeedoClass parent = sc.getSuper();
if (sc.inheritance.join == null) {
sc.inheritance.join = new SpeedoJoin();
}
SpeedoJoin join = sc.inheritance.join;
if (join.mainTable == null) {
join.mainTable = getRootTable(parent);
}
if (sc.mainTable == null) {
if (join.extTable == null) {
join.extTable = new SpeedoTable();
}
sc.mainTable = join.extTable;
} else {
join.extTable = sc.mainTable;
}
} else if (sc.inheritance.strategy == SpeedoInheritance.STRATEGY_SUBCLASS_TABLE) {
//allocate a temp table
sc.mainTable = new SpeedoTable();
} else {
throw new SpeedoException("Inheritance case not managed, class: "
+ sc.getSourceDesc());
}
}
if (sc.mainTable.name == null) {
allocateTableName(sc, sc.mainTable);
}
}
/**
* Visit inheritance elements discriminators, inherited fields, join to
* parent table, ...
*/
private void visitClassInheritance(SpeedoClass sc) throws SpeedoException {
if (sc.inheritance == null || sc.inheritance.superClassName == null) {
return;
}
if (sc.inheritance.isFilteredMapping()) {
if (scp.nmf.getNamingManager(sc).needInheritanceDiscriminator(sc)) {
SpeedoClass ancestor = sc.getAncestor();
if (ancestor.inheritance == null || ancestor.inheritance.discriminator == null) {
throw new SpeedoException("Filtered inheritance requires discriminator defined at root level: " + ancestor.getSourceDesc());
}
if (sc.inheritance.discriminatorValues == null) {
throw new SpeedoException("Filtered inheritance requires discriminator values defined for each sub class: " + sc.getSourceDesc());
}
}
} else if (sc.inheritance.isHorizontalMapping()) {
//maps all field of all parents
List parents = sc.getParents();
for (Iterator parentIt = parents.iterator(); parentIt.hasNext();) {
SpeedoClass parent = (SpeedoClass) parentIt.next();
for (Iterator fieldIt = parent.fields.values().iterator(); fieldIt.hasNext();) {
mapHorizontalInheritedField((SpeedoField) fieldIt.next(), sc);
}
}
} else if (sc.inheritance.isVerticalMapping()) {
//TODO: join columns between join.mainTable and join.extTable
//TODO: discriminator
}
}
/**
* Visit identity column(s) when there is no primary key field
*/
private void visitIdentityColumn(SpeedoClass sc) throws SpeedoException {
if (sc.identity.columns != null || sc.getPKFields().size() > 0) {
return;
}
//no pk fields and no column defined in sc.identity
NamingManager nm = scp.nmf.getNamingManager(sc);
SpeedoColumn[] cols = nm.getDefaultColumn(sc);
if (cols == null) {
throw new SpeedoException("no identity mapping for the class '"
+ sc.getFQName() + "'.");
}
sc.identity.setColumns(Arrays.asList(cols));
}
/**
* Visit primitive field. By default a primitive field is stored in the
* main table except if a join is specified. The default column name is
* the field name.
*/
private void visitPrimitiveField(SpeedoField sf) throws SpeedoException {
logger.log(BasicLevel.DEBUG, "\t\tvisit field primitive '" + sf.name + "'.");
if (sf.columns == null) {
logger.log(BasicLevel.DEBUG, "\t\tcreate new Column.");
sf.addColumn(new SpeedoColumn());
}
SpeedoColumn col = sf.columns[0];
if (col.table == null) {
if (sf.join == null) {
col.table = sf.moClass.mainTable;
} else {
col.table = sf.join.extTable;
}
logger.log(BasicLevel.DEBUG, "\t\tset column table: " + col.table.name);
}
if (col.name == null) {
col.name = sf.name;
logger.log(BasicLevel.DEBUG, "\t\tset column name: " + col.name);
}
}
/**
* Set the mapped-by visit for bidirectional relationship.
* @param sf is a persistent field.
*/
private void visitFieldMappedBy(SpeedoField sf) throws SpeedoException {
if (sf.relationType == SpeedoField.NO_BI_RELATION) {
logger.log(BasicLevel.DEBUG, "\t\t\tNo bidirectional relation.");
} else {
SpeedoField rf = sf.getReverseField();
boolean sfHasMapping = sf.columns != null || (sf.join != null && !sf.join.columns.isEmpty());
boolean rfHasMapping = rf.columns != null || (rf.join != null && !rf.join.columns.isEmpty());
if (sfHasMapping) {
if (rfHasMapping) {
logger.log(BasicLevel.DEBUG, "\t\t\tOR Mapping already defined both side.");
sf.mappedByReversefield = false;
rf.mappedByReversefield = false;
} else {
logger.log(BasicLevel.DEBUG, "\t\t\tthe field contains an OR Mapping.");
sf.mappedByReversefield = false;
rf.mappedByReversefield = true;
}
} else {
if (rfHasMapping) {
logger.log(BasicLevel.DEBUG, "\t\t\tthe reverse field contains an OR Mapping.");
sf.mappedByReversefield = true;
rf.mappedByReversefield = false;
} else {
if (sf.relationType == SpeedoField.MANY_ONE_BI_RELATION) {
//for MANY_ONE relations, when no mappedBy is specifed, it is
// simpler that the collection reference is mapped by simple
// reference
rf.mappedByReversefield = true;
sf.mappedByReversefield = false;
logger.log(BasicLevel.DEBUG, "\t\t\tfield is a the MANY side of the relation, then it must contains the OR Mapping.");
} else if (sf.relationType == SpeedoField.ONE_MANY_BI_RELATION) {
rf.mappedByReversefield = false;
sf.mappedByReversefield = true;
logger.log(BasicLevel.DEBUG, "\t\t\tfield is a the ONE side of the relation, then it is mapped by the reverse field.");
} else {
sf.mappedByReversefield = !rf.mappedByReversefield;
logger.log(BasicLevel.DEBUG, "\t\t\tfield is "
+ (sf.mappedByReversefield ? "" : " NOT")
+ " mapped by the reverse field.");
}
}
}
}
}
/**
* Visit Class reference field. Manages the case of the foreign key is
* managed by reverse field or not.
*/
private void visitClassRefField(SpeedoField sf) throws SpeedoException {
logger.log(BasicLevel.DEBUG, "\t\tvisit field class reference '" + sf.name + "'.");
visitClassRefFieldExtension(sf);
visitFieldMappedBy(sf);
SpeedoClass rclass = sf.getReferencedClass();
if (sf.mappedByReversefield) {
SpeedoField rf = sf.getReverseField();
logger.log(BasicLevel.DEBUG, "\t\tfield '" + sf.name
+ "' is mapped by reverse field: "
+ rf.getFQFieldName());;
visitSpeedoClass(rclass, new VisitRequired(rf));
computeFieldFromReverse(sf);
} else {
SpeedoTable table;
if (sf.join == null) {
table = sf.moClass.mainTable;
} else {
table = sf.join.extTable;
}
if (sf.columns == null) {
logger.log(BasicLevel.DEBUG, "\t\tfield '" + sf.name
+ "' requires pk fields of the class "
+ rclass.getFQName());
visitSpeedoClass(rclass, VisitRequired.BASE);
sf.columns = getFKColumn(rclass, sf.name + "_", table);
} else {
for (int i = 0; i < sf.columns.length; i++) {
sf.columns[i].table = table;
computeTargetColumn(sf, i, rclass);
}
}
}
}
/**
* Computes field mapping from its reverse field.
*/
private void computeFieldFromReverse(SpeedoField sf)throws SpeedoException {
SpeedoField rField = sf.getReverseField();
sf.join = new SpeedoJoin();
sf.join.mainTable = sf.moClass.mainTable;
if (rField.join == null) {
//compute the sf.column from the pk column of the referenced class
sf.join.extTable = rField.moClass.mainTable;
sf.columns = getFKColumn(rField.moClass, "", sf.join.extTable);
} else {
sf.join.extTable = rField.join.extTable;
//compute the sf.column from the pk column of the referenced class
sf.columns = new SpeedoColumn[rField.join.columns.size()];
int i = 0;
for (Iterator jcolIt = rField.join.columns.iterator(); jcolIt.hasNext();) {
SpeedoJoinColumn jcol = (SpeedoJoinColumn) jcolIt.next();
sf.columns[i] = (SpeedoColumn) jcol.column.clone();
sf.columns[i].targetColumn = jcol.targetColumn;
sf.columns[i].targetField = jcol.targetField;
i++;
}
}
//compute the join
sf.join.columns.clear();
for (int i = 0; i < rField.columns.length; i++) {
sf.join.columns.add(new SpeedoJoinColumn(rField.columns[i]));
}
}
/**
* Gets a list of SpeedoJoinColumn joining the identifier column of
* a referenced class
* @param rclass is the referenced
* @param colPrefix is prefix to the foreign key column name
* @param table is the table of the foreign key column
* @return list of SpeedoJoinColumn
*/
private List getFKJoinColumn(SpeedoClass rclass,
String colPrefix,
SpeedoTable table) {
SpeedoColumn[] fkCols = getFKColumn(rclass, colPrefix, table);
ArrayList res = new ArrayList(fkCols.length);
for (int i = 0; i < fkCols.length; i++) {
res.add(new SpeedoJoinColumn(fkCols[i]));
}
return res;
}
/**
* Gets a list of SpeedoColumn joining the identifier column of
* a referenced class.
* @param rclass is the referenced
* @param colPrefix is prefix to the foreign key column name
* @param table is the table of the foreign key column
* @return
*/
private SpeedoColumn[] getFKColumn(SpeedoClass rclass,
String colPrefix,
SpeedoTable table) {
SpeedoColumn[] pkColumns = getIdColumns(rclass);
SpeedoColumn[] columns = new SpeedoColumn[pkColumns.length];
for(int i=0; i<pkColumns.length; i++) {
//create new SpeedoColumn instance targeting pk column
// with the same description (sql type, length, scale, ...)
columns[i] = new SpeedoColumn();
columns[i].targetColumn = pkColumns[i].name;
columns[i].name = colPrefix + columns[i].targetColumn;
columns[i].table = table;
columns[i].sqlType = pkColumns[i].sqlType;
columns[i].scale = pkColumns[i].scale;
columns[i].length = pkColumns[i].length;
}
return columns;
}
/**
* Gets the identifier column(s) of a persistent class
* @param sc is a persistent class
* @return the identifier column(s) of a persistent class
*/
private SpeedoColumn[] getIdColumns(SpeedoClass sc) {
Collection pkFields = sc.getPKFields();
SpeedoColumn[] columns;
if (pkFields.isEmpty()) {
//identifier is based on visible persistent field(s)
columns = new SpeedoColumn[sc.identity.columns.length];
for(int i=0; i<sc.identity.columns.length; i++) {
columns[i] = sc.identity.columns[i].column;
}
} else {
//identifier is based on hidden field(s) (ex: data store id)
int i = 0;
columns = new SpeedoColumn[pkFields.size()];
for (Iterator it = pkFields.iterator(); it.hasNext();) {
SpeedoField pkField = (SpeedoField) it.next();
columns[i] = pkField.columns[0];
i++;
}
}
return columns;
}
/**
* Visit GenClassRef field (field referencing a collection or a map of
* stuff).
* @param sf is a persistent field referencing a collection, a map of stuff
* (persistent objects or primitive elements)
*/
private void visitGenClassRefField(SpeedoField sf) throws SpeedoException {
logger.log(BasicLevel.DEBUG, "\t\tvisit field generic class reference '" + sf.name + "'.");
visitGenClassRefFieldExtension(sf);
visitFieldMappedBy(sf);
if (sf.mappedByReversefield) {
SpeedoField rf = sf.getReverseField();
logger.log(BasicLevel.DEBUG, "\t\tfield '" + sf.name
+ "' is mapped by reverse field: "
+ rf.getFQFieldName());
visitSpeedoClass(sf.getReferencedClass(), new VisitRequired(rf));
computeFieldFromReverse(sf);
// do not forget index in case of map indexed by field of the
// referenced class
visitGenClassIndex(sf);
return;
}
boolean joinCreated = sf.join == null;
if (joinCreated) {
//create the join between mainTable and the genClass table
sf.join = new SpeedoJoin();
//sf.moClass.addJoin(sf.join);
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tCreate SpeedoJoin");
}
}
if (sf.join.mainTable == null) {
sf.join.mainTable = sf.moClass.mainTable;
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tDefine the main table on join: " + sf.join.mainTable.name);
}
}
if (sf.join.extTable == null) {
//compute the table of the genclass with regards to the relation type
switch (sf.relationType) {
case SpeedoField.MANY_MANY_BI_RELATION:
SpeedoField rfield = sf.getReverseField();
if (rfield.join != null && rfield.join.extTable != null) {
sf.join.extTable = rfield.join.extTable;
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tUse table of reverse field: " + sf.join.extTable.name);
}
} else {
sf.join.extTable = new SpeedoTable();
sf.join.extTable.name = sf.moClass.name + "_" + sf.name;
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tDefine the join table of the relation: " + sf.join.extTable.name);
}
}
break;
case SpeedoField.ONE_MANY_BI_RELATION:
rfield = sf.getReverseField();
if (rfield.join != null && rfield.join.extTable != null) {
sf.join.extTable = rfield.join.extTable;
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tUse table of reverse field: " + sf.join.extTable.name);
}
} else {
sf.join.extTable = rfield.moClass.mainTable;
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\tUse the table of the reference class: " + sf.join.extTable.name);
}
}
break;
default:
sf.join.extTable = new SpeedoTable();
sf.join.extTable.name = sf.moClass.name + "_" + sf.name;
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tDefine the GC table: " + sf.join.extTable.name);
}
break;
}
sf.join.extTable.join = sf.join;
}
if (sf.join.columns.isEmpty()) {
//Compute the join between the table of the owner class and the
// table of the genclass
sf.join.columns.addAll(getFKJoinColumn(sf.moClass, "", sf.join.extTable));
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tDefine the join column: \n" + sf.join.columns);
}
} else {
for (Iterator it = sf.join.columns.iterator(); it.hasNext();) {
SpeedoJoinColumn jc = (SpeedoJoinColumn) it.next();
if (jc.targetColumn == null) {
computeTargetJoinColumn(sf, jc);
}
}
}
if (joinCreated && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tCreated " + sf.join.toString());
}
//map the index of the genclass
visitGenClassIndex(sf);
//map the element of the genclass
visitGenClassElement(sf);
}
/**
* Visit genclass index. Indexes can be find for List or Map implementation.
* @param sf a persistent field.
*/
private void visitGenClassIndex(SpeedoField sf) throws SpeedoException {
if (sf.jdoTuple instanceof SpeedoCollection) {
SpeedoCollection collec = (SpeedoCollection) sf.jdoTuple;
if (collec.indexColumns == null
&& List.class.isAssignableFrom(getGCClass(sf))) {
collec.indexColumns = new SpeedoColumn("idx", sf.join.extTable);
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"\t\t\tCreate column for the list index"
+ collec.indexColumns.toString());
}
}
} else if (sf.jdoTuple instanceof SpeedoMap) {
SpeedoMap map = (SpeedoMap) sf.jdoTuple;
if (map.keyColumns == null) {
map.keyColumns = new SpeedoColumn("idx", sf.join.extTable);
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "\t\t\tCreate column for the map key"
+ map.keyColumns.toString());
}
}
}
}
/**
* Visit gen class element. The element can be a primitive element
* or a reference to a persistent class.
* @param sf is the SpeedoField meta object representing the genclass
*/
private void visitGenClassElement(SpeedoField sf) throws SpeedoException {
SpeedoClass rclass = sf.getReferencedClass();
if (sf.columns == null) {
if (rclass == null) {
//primitive element
sf.addColumn(new SpeedoColumn("element", sf.join.extTable));
} else {
logger.log(BasicLevel.DEBUG, "\t\tfield '" + sf.name
+ "' requires pk fields of the class "
+ rclass.getFQName());
visitSpeedoClass(rclass, VisitRequired.BASE);
//persistent class ==> classRef
String prefix = "";
if (sf.join.extTable != rclass.mainTable) {
prefix = "elem_";
}
sf.columns = getFKColumn(rclass, prefix, sf.join.extTable);
}
if (logger.isLoggable(BasicLevel.DEBUG)) {
StringBuffer sb = new StringBuffer();
sb.append("\t\t\tCreate ").append(sf.columns.length);
sb.append(" column(s) for the gen class element :\n");
for (int i = 0; i < sf.columns.length; i++) {
sb.append("\t\t-").append(sf.columns[i]).append("\n");
}
logger.log(BasicLevel.DEBUG, sb.toString());
}
} else {
if (rclass == null) {
//primitive element
if (sf.columns[0].name == null) {
sf.columns[0].name = "element";
}
if (sf.columns[0].table == null) {
sf.columns[0].table = sf.join.extTable;
}
} else {
//persistent class ==> classRef
SpeedoColumn[] cols = getFKColumn(rclass, "elem_", sf.join.extTable);
for (int i = 0; i < sf.columns.length; i++) {
if (sf.columns[i].name == null) {
sf.columns[i].name = cols[i].name;
}
if (sf.columns[i].targetColumn == null) {
computeTargetColumn(sf, i, rclass);
}
if (sf.columns[i].table == null) {
sf.columns[i].table = sf.join.extTable;
}
}
}
}
}
/**
* Computes the targetColumn field of the sf.column[colIdx] if it is
* required.
* @param sf is the SpeedoCommonField holding the column definition
* @param colIdx is the index of the column among sf.columns array
* @param rclass is the referenced persistent class. The target column are
* the identifier column of the referenced class.
* @throws SpeedoException
*/
private void computeTargetColumn(SpeedoCommonField sf,
int colIdx,
SpeedoClass rclass) throws SpeedoException {
if (sf.columns[colIdx].targetColumn != null) {
return;
}
if (sf.columns[colIdx].targetField == null) {
//both are null
if (sf.columns.length == 1) {
//compute the target column from the unique pk field
try {
SpeedoField pkField = rclass.getUniquePKField();
sf.columns[colIdx].targetColumn = pkField.columns[0].name;
} catch (SpeedoException e) {
throw new SpeedoException("Bad number of column specified for the " + sf.getSourceDesc(), e);
}
} else {
SpeedoException se = new SpeedoException("Target column is required for the reference " + sf.getSourceDesc());
logger.log(BasicLevel.ERROR, se.getMessage(), se);
throw se;
}
} else {
//compute the target column from the target field
SpeedoField pkField = rclass.getField(sf.columns[colIdx].targetField);
sf.columns[colIdx].targetColumn = pkField.columns[0].name;
}
}
private void computeTargetJoinColumn(SpeedoCommonField sf,
SpeedoJoinColumn jc) throws SpeedoException {
int nbcol = sf.join.columns.size();
if (jc.targetField == null) {
//both are null
if (nbcol == 1) {
//compute the target column from the unique pk field
SpeedoField pkField = sf.moClass.getUniquePKField();
jc.targetColumn = pkField.columns[0].name;
} else {
SpeedoException se = new SpeedoException("Target column is required for join column of the reference " + sf.getSourceDesc());
logger.log(BasicLevel.ERROR, se.getMessage(), se);
throw se;
}
} else {
//compute the target column from the target field
SpeedoField pkField = sf.moClass.getField(jc.targetField);
jc.targetColumn = pkField.columns[0].name;
}
}
/**
* Get the type of the persistent field.
* @return the java.lang.Class representing the type of the field.
*/
private Class getGCClass(SpeedoField sf) throws SpeedoException {
try {
return Class.forName(sf.type());
} catch (ClassNotFoundException e) {
throw new SpeedoException("Class loading problem: ", e);
}
}
/**
* Creates a new table name for a SpeedoTable
* @param sc is the SpeedoClass that has the table
* @param t is the table without name
*/
private void allocateTableName(SpeedoClass sc, SpeedoTable t) {
if (t.name == null) {
if (sc.mainTable == t) {
t.name = sc.name.toUpperCase();
} else {
for (int i = 0; i < sc.joinToExtTables.length; i++) {
if (sc.joinToExtTables[i].extTable == t) {
t.name = sc.name.toUpperCase() + "_EXT_" + i;
return;
}
}
t.name = sc.name.toUpperCase();
}
}
}
/**
* Builds, if required, the mapping of an inherited field.
* @param sif
*/
private void mapHorizontalInheritedField(SpeedoField sf,
SpeedoClass sc) throws SpeedoException {
SpeedoInheritedField sif = (SpeedoInheritedField)
sc.inheritance.remappedInheritedFields.get(sf.getFQFieldName());
if (sif == null) {
sif = sc.inheritance.newSpeedoInheritedField(sf);
}
if (sf.jdoTuple == null) {
SpeedoClass rclass = sf.getReferencedClass();
//primitive field or simple reference to a persistent class
if (rclass != null && sf.join != null
&& sf.relationType == SpeedoField.ONE_ONE_BI_RELATION
&& sf.mappedByReversefield) {
// the classref belongs a bidirectionnal relationship
// ONE-ONE. In addition the foreign key is hold by the table
// of the referenced class
setJoinNColsFromParent(sif);
} else {
if (sif.columns == null) {
// ClassRef or primtive element to map localy
for (int i = 0; i < sf.columns.length; i++) {
//Same column definition but in the table of the class
SpeedoColumn col = (SpeedoColumn) sf.columns[i].clone();
col.table = sif.moClass.mainTable;
sif.addColumn(col);
}
} else {
if (rclass != null) {
for (int i = 0; i < sif.columns.length; i++) {
computeTargetColumn(sif, i, rclass);
}
}
}
}
} else {
//reference to a generic class (Collection, Set, Map, ..)
setJoinNColsFromParent(sif);
//TODO: support index/key
}
}
private void setJoinNColsFromParent(SpeedoInheritedField sif) {
SpeedoField sf = sif.inheritedField;
if (sif.columns == null) {
//Use the same column for the genclass value
sif.columns = sf.columns;
}
if (sif.join == null) {
sif.join = new SpeedoJoin();
sif.join.extTable = sf.join.extTable;
sif.join.mainTable = sf.moClass.mainTable;
// Use the same join column name but with the targeted column
// of the current table. We suppose than the id has the same
// structure
sif.join.columns = getFKJoinColumn(sf.moClass, "", sif.join.extTable);
if (sif.join.columns.size() == 1) {
((SpeedoJoinColumn) sif.join.columns.get(0)).column.name =
((SpeedoJoinColumn) sf.join.columns.get(0)).column.name;
} else {
for (Iterator it = sf.moClass.getPKFields().iterator(); it.hasNext();) {
SpeedoField pkField = (SpeedoField) it.next();
SpeedoJoinColumn parentjc = sf.getFKJoinColumn(pkField.columns[0].name);
SpeedoJoinColumn newjc = sif.getFKJoinColumn(pkField.columns[0].name);
newjc.column.name = parentjc.column.name;
}
}
}
}
/**
* Get the root table of a persistent class. If the class has not
* inheritance the root table is the maintable. If the inheritance mapping
* is vertical, the root table is the one of the parent.
*/
private SpeedoTable getRootTable(SpeedoClass sc) {
if (sc.inheritance != null && sc.inheritance.isVerticalMapping()) {
return sc.inheritance.join.mainTable;
} else {
return sc.mainTable;
}
}
/**
* Visit extensions of field referencing a persistent class. This method
* converts old Extensions 'source-foreign-keys' and 'target-foreign-keys'
* to mapping definition in the Speedo meta information.
* @param sf a persistent field referencing a persistent class.
* @see SpeedoProperties#SOURCE_FK
* @see SpeedoProperties#TARGET_FK
*/
public void visitClassRefFieldExtension(SpeedoField sf) throws SpeedoException {
SpeedoClass rclass = sf.getReferencedClass();
String sfk = sf.getExtensionValueByKey(SpeedoProperties.SOURCE_FK);
String tfk = sf.getExtensionValueByKey(SpeedoProperties.TARGET_FK);
if (sfk == null && tfk == null) {
return;
}
if (sfk != null && sf.relationType == SpeedoField.ONE_ONE_BI_RELATION) {
//The relation is mapped by the field having the foreign key
return;
}
if (tfk != null) {
SpeedoColumn[] cols = getFKColumn(tfk, sf.moClass.mainTable, sf.getReferencedClass());
if (sf.columns != null && cols.length == sf.columns.length) {
for (int i = 0; i < cols.length; i++) {
sf.columns[i].merge(cols[i]);
}
} else {
sf.columns = cols;
}
} else if (sfk != null) { //backward reference
sf.join = new SpeedoJoin();
sf.join.mainTable = sf.moClass.mainTable;
sf.join.extTable = rclass.mainTable;
//compute the sf.column from the pk column of the referenced class
Collection pkFields = rclass.getPKFields();
if (pkFields.isEmpty()) {
sf.columns = new SpeedoColumn[rclass.identity.columns.length];
for(int i=0; i<rclass.identity.columns.length; i++) {
sf.columns[i] = rclass.identity.columns[i].column;
}
} else {
int i = 0;
sf.columns = new SpeedoColumn[pkFields.size()];
for (Iterator it = pkFields.iterator(); it.hasNext();) {
SpeedoField pkField = (SpeedoField) it.next();
sf.columns[i] = pkField.columns[0];
i++;
}
}
//compute the join columns
sf.join.columns.addAll(getFKJoinColumn(sfk, rclass.mainTable, sf.moClass));
}
logger.log(BasicLevel.DEBUG, "Field '" + sf.name
+ "' has deprecated extension(s): "
+ "\n\t-(" + SpeedoProperties.SOURCE_FK + "=" + sfk
+ "\n\t-" + SpeedoProperties.TARGET_FK + "=" + tfk
+ "\nExtensions have been converted:"
+ "\n\t- column:" + sf.printColumns()
+ "\n\t- join:" + sf.join
);
}
/**
* Visit extensions of field referencing a genclass. This method
* converts old Extensions 'source-foreign-keys', 'target-foreign-keys'
* and 'join-table' to mapping definition in the Speedo meta information.
* @param sf a persistent field referencing a genclass.
* @see SpeedoProperties#SOURCE_FK
* @see SpeedoProperties#TARGET_FK
* @see SpeedoProperties#JOIN_TABLE
*/
public void visitGenClassRefFieldExtension(SpeedoField sf) throws SpeedoException {
String sfk = sf.getExtensionValueByKey(SpeedoProperties.SOURCE_FK);
String tfk = sf.getExtensionValueByKey(SpeedoProperties.TARGET_FK);
String jt = sf.getExtensionValueByKey(SpeedoProperties.JOIN_TABLE);
if (sfk == null && tfk == null && jt == null) {
return;
}
if (sfk != null && tfk == null && jt == null
&& sf.relationType == SpeedoField.ONE_MANY_BI_RELATION) {
//The relation is mapped by the field having the foreign key
return;
}
sf.join = new SpeedoJoin();
sf.join.mainTable = sf.moClass.mainTable;
//sf.moClass.addJoin(sf.join);
if (jt != null) {
sf.join.extTable = new SpeedoTable();
sf.join.extTable.name = jt;
}
if (tfk != null && (sf.columns == null || sf.columns[0].name == null)) {
//compute the value column(s)
sf.columns = getFKColumn(tfk, sf.join.extTable, sf.getReferencedClass());
}
if (sfk != null && sf.join.columns.isEmpty()) {
//compute the join column(s)
sf.join.columns.addAll(getFKJoinColumn(sfk, sf.join.extTable, sf.moClass));
}
logger.log(BasicLevel.DEBUG, "Field '" + sf.name
+ "' has deprecated extension(s): "
+ "\n\t-(" + SpeedoProperties.SOURCE_FK + "=" + sfk
+ "\n\t-" + SpeedoProperties.TARGET_FK + "=" + tfk
+ "\n\t" + SpeedoProperties.JOIN_TABLE + "=" + jt
+ "\nExtensions have been converted:"
+ "\n\t- column:" + sf.printColumns()
+ "\n\t- join:" + sf.join
);
}
/**
* Gets array of new SpeedoColumn representing a join to a peristent class.
* @param pk2tfk is the map defining the name of the foreign key column from
* the primary key column (key=String pkColName/ value=String fkColName)
* @param table is the table hosting the foreign key column
* @param rclass is the referenced class having pk column
*/
private SpeedoColumn[] getFKColumn(String pk2tfk, SpeedoTable table, SpeedoClass rclass) {
Map map = getPk2Fk(pk2tfk);
SpeedoColumn[] columns = new SpeedoColumn[map.size()];
int i = 0;
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry me = (Map.Entry) it.next();
columns[i] = new SpeedoColumn();
columns[i].name = (String) me.getValue();
columns[i].table = table;
columns[i].targetColumn = (String) me.getKey();
if (rclass != null) {
SpeedoColumn pkcol = rclass.getColumn(columns[i].targetColumn, true);
if (pkcol != null) {
columns[i].sqlType = pkcol.sqlType;
columns[i].scale = pkcol.scale;
columns[i].length = pkcol.length;
}
}
i++;
}
return columns;
}
/**
* Gets List of new SpeedoJoinColumn representing a join to a peristent class.
* @param pk2tfk is the map defining the name of the foreign key column from
* the primary key column (key=String pkColName/ value=String fkColName)
* @param table is the table hosting the foreign key column
* @param rclass is the referenced class having pk column
*/
private List getFKJoinColumn(String pk2sfk, SpeedoTable table, SpeedoClass rclass) {
SpeedoColumn[] cols = getFKColumn(pk2sfk, table, rclass);
ArrayList res = new ArrayList(cols.length);
for (int i = 0; i < cols.length; i++) {
res.add(new SpeedoJoinColumn(cols[i]));
}
return res;
}
/**
* Converts the value of the extension 'source-foreign-keys' and
* 'target-foreign-keys' to a map
* @param fks
* @return a map (key=String pkColName / Value=String fkColName)
*/
private Map getPk2Fk(String fks) {
Map res = new HashMap();
StringTokenizer st = new StringTokenizer(fks, "=,:;/", false);
String pk = null;
while(st.hasMoreTokens()) {
String tok = st.nextToken();
tok = tok.trim();
if (pk == null) {
pk = tok;
} else {
res.put(pk, tok);
pk = null;
}
}
return res;
}
}