You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pc...@apache.org on 2006/07/19 23:35:07 UTC
svn commit: r423615 [13/44] - in /incubator/openjpa/trunk: ./
openjpa-jdbc-5/ openjpa-jdbc-5/src/ openjpa-jdbc-5/src/main/
openjpa-jdbc-5/src/main/java/ openjpa-jdbc-5/src/main/java/org/
openjpa-jdbc-5/src/main/java/org/apache/ openjpa-jdbc-5/src/main/...
Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingInfo.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingInfo.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingInfo.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingInfo.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,1748 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.openjpa.jdbc.meta;
+
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.ColumnIO;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.jdbc.schema.Index;
+import org.apache.openjpa.jdbc.schema.PrimaryKey;
+import org.apache.openjpa.jdbc.schema.Schema;
+import org.apache.openjpa.jdbc.schema.SchemaGroup;
+import org.apache.openjpa.jdbc.schema.Schemas;
+import org.apache.openjpa.jdbc.schema.Table;
+import org.apache.openjpa.jdbc.schema.Unique;
+import org.apache.openjpa.jdbc.sql.DBDictionary;
+import org.apache.openjpa.lib.log.Log;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.meta.MetaDataContext;
+import org.apache.openjpa.util.MetaDataException;
+import serp.util.Strings;
+
+/**
+ * Base class storing raw mapping information; defines utility methods for
+ * converting raw mapping information to full mapping to the schema.
+ *
+ * @author Abe White
+ */
+public abstract class MappingInfo {
+
+ public static final int JOIN_NONE = 0;
+ public static final int JOIN_FORWARD = 1;
+ public static final int JOIN_INVERSE = 2;
+
+ private static final Object NULL = new Object();
+
+ private static final Localizer _loc = Localizer.forPackage
+ (MappingInfo.class);
+
+ private String _strategy = null;
+ private List _cols = null;
+ private Index _idx = null;
+ private Unique _unq = null;
+ private ForeignKey _fk = null;
+ private boolean _canIdx = true;
+ private boolean _canUnq = true;
+ private boolean _canFK = true;
+ private int _join = JOIN_NONE;
+ private ColumnIO _io = null;
+
+ /**
+ * Mapping strategy name.
+ */
+ public String getStrategy() {
+ return _strategy;
+ }
+
+ /**
+ * Mapping strategy name.
+ */
+ public void setStrategy(String strategy) {
+ _strategy = strategy;
+ }
+
+ /**
+ * Raw column data.
+ */
+ public List getColumns() {
+ return (_cols == null) ? Collections.EMPTY_LIST : _cols;
+ }
+
+ /**
+ * Raw column data.
+ */
+ public void setColumns(List cols) {
+ _cols = cols;
+ }
+
+ /**
+ * Raw index.
+ */
+ public Index getIndex() {
+ return _idx;
+ }
+
+ /**
+ * Raw index.
+ */
+ public void setIndex(Index idx) {
+ _idx = idx;
+ }
+
+ /**
+ * The user can mark columns as explicitly non-indexable.
+ */
+ public boolean canIndex() {
+ return _canIdx;
+ }
+
+ /**
+ * The user can mark columns as explicitly non-indexable.
+ */
+ public void setCanIndex(boolean indexable) {
+ _canIdx = indexable;
+ }
+
+ /**
+ * Raw foreign key information.
+ */
+ public ForeignKey getForeignKey() {
+ return _fk;
+ }
+
+ /**
+ * Raw foreign key information.
+ */
+ public void setForeignKey(ForeignKey fk) {
+ _fk = fk;
+ if (fk != null && _join == JOIN_NONE)
+ _join = JOIN_FORWARD;
+ }
+
+ /**
+ * The user can mark columns as explicitly not having a foreign key.
+ */
+ public boolean canForeignKey() {
+ return _canFK;
+ }
+
+ /**
+ * The user can mark columns as explicitly not having a foreign key.
+ */
+ public void setCanForeignKey(boolean fkable) {
+ _canFK = fkable;
+ }
+
+ /**
+ * Raw unique constraint information.
+ */
+ public Unique getUnique() {
+ return _unq;
+ }
+
+ /**
+ * Raw unique constraint information.
+ */
+ public void setUnique(Unique unq) {
+ _unq = unq;
+ }
+
+ /**
+ * The user can mark columns as explicitly not having a unique constraint.
+ */
+ public boolean canUnique() {
+ return _canUnq;
+ }
+
+ /**
+ * The user can mark columns as explicitly not having a unique constraint.
+ */
+ public void setCanUnique(boolean uniquable) {
+ _canUnq = uniquable;
+ }
+
+ /**
+ * I/O for the columns created by the last call to {@link #createColumns},
+ * or for the foreign key created by the last call to
+ * {@link #createForeignKey}. This is also expected to be set correctly
+ * prior to calls to {@link #syncColumns} and {@link #syncForeignKey}.
+ */
+ public ColumnIO getColumnIO() {
+ return _io;
+ }
+
+ /**
+ * I/O for the columns created by the last call to {@link #createColumns},
+ * or for the foreign key created by the last call to
+ * {@link #createForeignKey}. This is also expected to be set correctly
+ * prior to calls to {@link #syncColumns} and {@link #syncForeignKey}.
+ */
+ public void setColumnIO(ColumnIO io) {
+ _io = io;
+ }
+
+ /**
+ * Direction of the join that the columns of this mapping info form. This
+ * is usually automatically set by {@link #createForeignKey}. This flag
+ * is also expected to be set correctly prior to calls to
+ * {@link #syncForeignKey} if the join is inversed.
+ */
+ public int getJoinDirection() {
+ return _join;
+ }
+
+ /**
+ * Direction of the join that the columns of this mapping info form. This
+ * is usually automatically set by {@link #createForeignKey}. This flag
+ * is also expected to be set correctly prior to calls to
+ * {@link #syncForeignKey} if the join is inversed.
+ */
+ public void setJoinDirection(int join) {
+ _join = join;
+ }
+
+ /**
+ * Clear all mapping information.
+ */
+ public void clear() {
+ clear(true);
+ }
+
+ /**
+ * Clear mapping information.
+ *
+ * @param canFlags whether to clear information about whether we
+ * can place indexed, foreign keys, etc on this mapping
+ */
+ protected void clear(boolean canFlags) {
+ _strategy = null;
+ _cols = null;
+ _io = null;
+ _idx = null;
+ _unq = null;
+ _fk = null;
+ _join = JOIN_NONE;
+ if (canFlags) {
+ _canIdx = true;
+ _canFK = true;
+ _canUnq = true;
+ }
+ }
+
+ /**
+ * Copy missing info from the instance to this one.
+ */
+ public void copy(MappingInfo info) {
+ if (_strategy == null)
+ _strategy = info.getStrategy();
+ if (_canIdx && _idx == null) {
+ if (info.getIndex() != null)
+ _idx = info.getIndex();
+ else
+ _canIdx = info.canIndex();
+ }
+ if (_canUnq && _unq == null) {
+ if (info.getUnique() != null)
+ _unq = info.getUnique();
+ else
+ _canUnq = info.canUnique();
+ }
+ if (_canFK && _fk == null) {
+ if (info.getForeignKey() != null)
+ _fk = info.getForeignKey();
+ else
+ _canFK = info.canForeignKey();
+ }
+
+ List cols = getColumns();
+ List icols = info.getColumns();
+ if (!icols.isEmpty() && (cols.isEmpty()
+ || cols.size() == icols.size())) {
+ if (cols.isEmpty())
+ cols = new ArrayList(icols.size());
+ for (int i = 0; i < icols.size(); i++) {
+ if (cols.size() == i)
+ cols.add(new Column());
+ ((Column) cols.get(i)).copy((Column) icols.get(i));
+ }
+ setColumns(cols);
+ }
+ }
+
+ /**
+ * Return true if this info has columns, foreign key information, index
+ * information, etc.
+ */
+ public boolean hasSchemaComponents() {
+ return (_cols != null && !_cols.isEmpty())
+ || _idx != null
+ || _unq != null
+ || _fk != null
+ || !_canIdx
+ || !_canFK
+ || !_canUnq;
+ }
+
+ /**
+ * Assert that the user did not supply any columns, index, unique
+ * constraint, or foreign key for this mapping.
+ */
+ public void assertNoSchemaComponents(MetaDataContext context, boolean die) {
+ if (_cols == null || _cols.isEmpty()) {
+ assertNoIndex(context, die);
+ assertNoUnique(context, die);
+ assertNoForeignKey(context, die);
+ return;
+ }
+
+ String msg = _loc.get("unexpected-cols", context);
+ if (die)
+ throw new MetaDataException(msg);
+ context.getRepository().getLog().warn(msg);
+ }
+
+ /**
+ * Assert that this info has the given strategy or no strategy.
+ */
+ public void assertStrategy(MetaDataContext context, Object contextStrat,
+ Object expected, boolean die) {
+ if (contextStrat == expected)
+ return;
+
+ String strat;
+ if (contextStrat == null) {
+ if (_strategy == null)
+ return;
+ if (_strategy.equals(expected.getClass().getName()))
+ return;
+ if (expected instanceof Strategy
+ && _strategy.equals(((Strategy) expected).getAlias()))
+ return;
+ strat = _strategy;
+ } else if (contextStrat instanceof Strategy)
+ strat = ((Strategy) contextStrat).getAlias();
+ else
+ strat = contextStrat.getClass().getName();
+
+ String msg = _loc.get("unexpected-strategy", context, expected,
+ strat);
+ if (die)
+ throw new MetaDataException(msg);
+ context.getRepository().getLog().warn(msg);
+ }
+
+ /**
+ * Assert that the user did not try to place an index on this mapping.
+ */
+ public void assertNoIndex(MetaDataContext context, boolean die) {
+ if (_idx == null)
+ return;
+
+ String msg = _loc.get("unexpected-index", context);
+ if (die)
+ throw new MetaDataException(msg);
+ context.getRepository().getLog().warn(msg);
+ }
+
+ /**
+ * Assert that the user did not try to place a unique constraint on this
+ * mapping.
+ */
+ public void assertNoUnique(MetaDataContext context, boolean die) {
+ if (_unq == null)
+ return;
+
+ String msg = _loc.get("unexpected-unique", context);
+ if (die)
+ throw new MetaDataException(msg);
+ context.getRepository().getLog().warn(msg);
+ }
+
+ /**
+ * Assert that the user did not try to place a foreign key on this mapping.
+ */
+ public void assertNoForeignKey(MetaDataContext context, boolean die) {
+ if (_fk == null)
+ return;
+
+ String msg = _loc.get("unexpected-fk", context);
+ if (die)
+ throw new MetaDataException(msg);
+ context.getRepository().getLog().warn(msg);
+ }
+
+ /**
+ * Assert that the user did not try to join.
+ */
+ public void assertNoJoin(MetaDataContext context, boolean die) {
+ boolean join = false;
+ if (_cols != null) {
+ Column col;
+ for (int i = 0; !join && i < _cols.size(); i++) {
+ col = (Column) _cols.get(i);
+ if (col.getTarget() != null)
+ join = true;
+ }
+ }
+ if (!join)
+ return;
+
+ String msg = _loc.get("unexpected-join", context);
+ if (die)
+ throw new MetaDataException(msg);
+ context.getRepository().getLog().warn(msg);
+ }
+
+ /**
+ * Find or generate a table for a mapping.
+ *
+ * @param context the mapping that uses the table
+ * @param def default table name provider
+ * @param schema default schema if known, or null
+ * @param given given table name
+ * @param adapt whether we can alter the schema or mappings
+ */
+ public Table createTable(MetaDataContext context, TableDefaults def,
+ Schema schema, String given, boolean adapt) {
+ MappingRepository repos = (MappingRepository) context.getRepository();
+ if (given == null && (def == null || (!adapt
+ && !repos.getMappingDefaults().defaultMissingInfo())))
+ throw new MetaDataException(_loc.get("no-table", context));
+
+ // if no given and adapting or defaulting missing info, use template
+ SchemaGroup group = repos.getSchemaGroup();
+ String schemaName = null;
+ if (given == null) {
+ if (schema == null) {
+ schemaName = Schemas.getNewTableSchema((JDBCConfiguration)
+ repos.getConfiguration());
+ schema = group.getSchema(schemaName);
+ if (schema == null)
+ schema = group.addSchema(schemaName);
+ }
+ given = def.get(schema);
+ }
+
+ // look for named table
+ Table table = group.findTable(given);
+ if (table != null)
+ return table;
+ if (!adapt)
+ throw new MetaDataException(_loc.get("bad-table", given, context));
+
+ // named table doesn't exist; figure out what schema to create new
+ // table in
+ int dotIdx = given.lastIndexOf('.');
+ if (dotIdx != -1) {
+ schema = null;
+ schemaName = given.substring(0, dotIdx);
+ given = given.substring(dotIdx + 1);
+ } else if (schema == null)
+ schemaName = Schemas.getNewTableSchema((JDBCConfiguration)
+ repos.getConfiguration());
+
+ if (schema == null) {
+ schema = group.getSchema(schemaName);
+ if (schema == null)
+ schema = group.addSchema(schemaName);
+ }
+
+ table = schema.getTable(given);
+ if (table == null)
+ table = schema.addTable(given);
+ return table;
+ }
+
+ /**
+ * Retrieve/create columns on the given table by merging the given
+ * template information with any user-provided information.
+ *
+ * @param context the mapping we're retrieving columns for
+ * @param prefix localized error message key prefix
+ * @param tmplates template columns
+ * @param table the table for the columns
+ * @param adapt whether we can modify the existing mapping or schema
+ */
+ protected Column[] createColumns(MetaDataContext context, String prefix,
+ Column[] tmplates, Table table, boolean adapt) {
+ assertTable(context, table);
+ if (prefix == null)
+ prefix = "generic";
+
+ // the user has to give the right number of expected columns for this
+ // mapping, or none at all if we're adapting. can't just given one of
+ // n columns because we don't know which of the n columns the info
+ // applies to
+ List given = getColumns();
+ boolean fill = ((MappingRepository) context.getRepository()).
+ getMappingDefaults().defaultMissingInfo();
+ if ((!given.isEmpty() || (!adapt && !fill))
+ && given.size() != tmplates.length)
+ throw new MetaDataException(_loc.get(prefix + "-num-cols",
+ context, String.valueOf(tmplates.length),
+ String.valueOf(given.size())));
+
+ Column[] cols = new Column[tmplates.length];
+ _io = null;
+ Column col;
+ for (int i = 0; i < tmplates.length; i++) {
+ col = (given.isEmpty()) ? null : (Column) given.get(i);
+ cols[i] = mergeColumn(context, prefix, tmplates[i], true, col,
+ table, adapt, fill);
+ setIOFromColumnFlags(col, i);
+ }
+ return cols;
+ }
+
+ /**
+ * Set the proper internal column I/O metadata for the given column's flags.
+ */
+ private void setIOFromColumnFlags(Column col, int i) {
+ if (col == null || (!col.getFlag(Column.FLAG_UNINSERTABLE)
+ && !col.getFlag(Column.FLAG_UNUPDATABLE)))
+ return;
+
+ if (_io == null)
+ _io = new ColumnIO();
+ _io.setInsertable(i, !col.getFlag(Column.FLAG_UNINSERTABLE));
+ _io.setUpdatable(i, !col.getFlag(Column.FLAG_UNUPDATABLE));
+ }
+
+ /**
+ * Assert that the given table is non-null.
+ */
+ private static void assertTable(MetaDataContext context, Table table) {
+ if (table == null)
+ throw new MetaDataException(_loc.get("unmapped", context));
+ }
+
+ /**
+ * Merge the given columns if possible.
+ *
+ * @param context the mapping we're retrieving columns for
+ * @param prefix localized error message key prefix
+ * @param tmplate template for expected column information
+ * @param compat whether the existing column type must be compatible
+ * with the type of the template column
+ * @param given the given column information from mapping info
+ * @param table the table for the columns
+ * @param adapt whether we can modify the existing mapping or schema
+ * @param fill whether to default missing column information
+ */
+ protected static Column mergeColumn(MetaDataContext context, String prefix,
+ Column tmplate, boolean compat, Column given, Table table,
+ boolean adapt, boolean fill) {
+ assertTable(context, table);
+
+ // if not adapting must provide column name at a minimum
+ String colName = (given == null) ? null : given.getName();
+ if (colName == null && !adapt && !fill)
+ throw new MetaDataException(_loc.get(prefix + "-no-col-name",
+ context));
+
+ // determine the column name based on given info, or template if none;
+ // also make sure that if the user gave a column name, he didn't try
+ // to put the column in an unexpected table
+ if (colName == null)
+ colName = tmplate.getName();
+ int dotIdx = colName.lastIndexOf('.');
+ if (dotIdx == 0)
+ colName = colName.substring(1);
+ else if (dotIdx != -1) {
+ findTable(context, colName.substring(0, dotIdx), table,
+ null, null);
+ colName = colName.substring(dotIdx + 1);
+ }
+
+ // find existing column
+ Column col = table.getColumn(colName);
+ if (col == null && !adapt)
+ throw new MetaDataException(_loc.get(prefix + "-bad-col-name",
+ context, colName, table));
+
+ MappingRepository repos = (MappingRepository) context.getRepository();
+ DBDictionary dict = repos.getDBDictionary();
+
+ // use information from template column by default, allowing any
+ // user-given specifics to override it
+ int type = tmplate.getType();
+ int size = tmplate.getSize();
+ if (type == Types.OTHER)
+ type = dict.getJDBCType(tmplate.getJavaType(), size == -1);
+ boolean ttype = true;
+ int otype = type;
+ String typeName = tmplate.getTypeName();
+ Boolean notNull = null;
+ if (tmplate.isNotNullExplicit())
+ notNull = (tmplate.isNotNull()) ? Boolean.TRUE : Boolean.FALSE;
+ int decimals = tmplate.getDecimalDigits();
+ String defStr = tmplate.getDefaultString();
+ boolean autoAssign = tmplate.isAutoAssigned();
+ boolean relationId = tmplate.isRelationId();
+ String targetField = tmplate.getTargetField();
+ if (given != null) {
+ // use given type if provided, but warn if it isn't compatible with
+ // the expected column type
+ if (given.getType() != Types.OTHER) {
+ ttype = false;
+ if (compat && !given.isCompatible(type, size)) {
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(_loc.get(prefix + "-incompat-col",
+ context, colName, Schemas.getJDBCName(type)));
+ }
+ otype = given.getType();
+ type = dict.getPreferredType(otype);
+ }
+ typeName = given.getTypeName();
+ size = given.getSize();
+ decimals = given.getDecimalDigits();
+
+ // leave this info as the template defaults unless the user
+ // explicitly turns it on in the given column
+ if (given.isNotNullExplicit())
+ notNull = (given.isNotNull()) ? Boolean.TRUE : Boolean.FALSE;
+ if (given.getDefaultString() != null)
+ defStr = given.getDefaultString();
+ if (given.isAutoAssigned())
+ autoAssign = true;
+ if (given.isRelationId())
+ relationId = true;
+ }
+
+ // default char column size if original type is char (test original
+ // type rather than final type because orig might be clob, translated
+ // to an unsized varchar, which is supported by some dbs)
+ if (size == 0 && (otype == Types.VARCHAR || otype == Types.CHAR))
+ size = dict.characterColumnSize;
+
+ // create column, or make sure existing column matches expected type
+ if (col == null) {
+ col = table.addColumn(colName);
+ col.setType(type);
+ } else if ((compat || !ttype) && !col.isCompatible(type, size)) {
+ // if existing column isn't compatible with desired type, die if
+ // can't adapt, else warn and change the existing column type
+ String msg = _loc.get(prefix + "-bad-col", context,
+ Schemas.getJDBCName(type), col.getDescription());
+ if (!adapt)
+ throw new MetaDataException(msg);
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(msg);
+
+ col.setType(type);
+ } else if (given != null && given.getType() != Types.OTHER) {
+ // as long as types are compatible, set column to expected type
+ col.setType(type);
+ }
+
+ // always set the java type and autoassign to expected values, even on
+ // an existing column, since we don't get this from the DB
+ if (compat)
+ col.setJavaType(tmplate.getJavaType());
+ else if (col.getJavaType() == JavaTypes.OBJECT) {
+ if (given != null && given.getJavaType() != JavaTypes.OBJECT)
+ col.setJavaType(given.getJavaType());
+ else
+ col.setJavaType(JavaTypes.getTypeCode
+ (Schemas.getJavaType(col.getType(), col.getSize(),
+ col.getDecimalDigits())));
+ }
+ col.setAutoAssigned(autoAssign);
+ col.setRelationId(relationId);
+ col.setTargetField(targetField);
+
+ // we need this for runtime, and the dynamic schema factory might
+ // not know it, so set it even if not adapting
+ if (defStr != null)
+ col.setDefaultString(defStr);
+ if (notNull != null)
+ col.setNotNull(notNull.booleanValue());
+
+ // add other details if adapting
+ if (adapt) {
+ if (typeName != null)
+ col.setTypeName(typeName);
+ if (size != 0)
+ col.setSize(size);
+ if (decimals != 0)
+ col.setDecimalDigits(decimals);
+ }
+ return col;
+ }
+
+ /**
+ * Find the table named by a column or target.
+ *
+ * @param context context for error messages, etc.
+ * @param name the table name, possibly including schema
+ * @param expected the expected table; may be null
+ * @param inverse the possible inverse table; may be null
+ * @param rel if we're finding the target table of a join, the
+ * joined-to type; allows us to also look in its superclass tables
+ */
+ private static Table findTable(MetaDataContext context, String name,
+ Table expected, Table inverse, ClassMapping rel) {
+ // is this the expected table?
+ if (expected == null && rel != null)
+ expected = rel.getTable();
+ if (expected != null && isTableName(name, expected))
+ return expected;
+
+ // check for inverse
+ if (inverse != null && isTableName(name, inverse))
+ return inverse;
+
+ // superclass table?
+ if (rel != null)
+ rel = rel.getJoinablePCSuperclassMapping();
+ while (rel != null) {
+ if (isTableName(name, rel.getTable()))
+ return rel.getTable();
+ rel = rel.getJoinablePCSuperclassMapping();
+ }
+
+ // none of the possible tables
+ throw new MetaDataException(_loc.get("col-wrong-table", context,
+ expected, name));
+ }
+
+ /**
+ * Return whether the given name matches the given table.
+ */
+ private static boolean isTableName(String name, Table table) {
+ return name.equalsIgnoreCase(table.getName())
+ || name.equalsIgnoreCase(table.getFullName());
+ }
+
+ /**
+ * Retrieve/create an index on the given columns by merging the given
+ * template information with any user-provided information.
+ *
+ * @param context the mapping we're retrieving an index for
+ * @param prefix localized error message key prefix
+ * @param tmplate template for expected index information
+ * @param cols the indexed columns
+ * @param adapt whether we can modify the existing mapping or schema
+ */
+ protected Index createIndex(MetaDataContext context, String prefix,
+ Index tmplate, Column[] cols, boolean adapt) {
+ if (prefix == null)
+ prefix = "generic";
+
+ // can't create an index if there are no cols
+ if (cols == null || cols.length == 0) {
+ if (_idx != null)
+ throw new MetaDataException(_loc.get(prefix
+ + "-no-index-cols", context));
+ return null;
+ }
+
+ // look for an existing index on these columns
+ Table table = cols[0].getTable();
+ Index[] idxs = table.getIndexes();
+ Index exist = null;
+ for (int i = 0; i < idxs.length; i++) {
+ if (idxs[i].columnsMatch(cols)) {
+ exist = idxs[i];
+ break;
+ }
+ }
+
+ // remove existing index?
+ if (!_canIdx) {
+ if (exist == null)
+ return null;
+ if (!adapt)
+ throw new MetaDataException(_loc.get(prefix + "-index-exists",
+ context));
+ table.removeIndex(exist);
+ return null;
+ }
+
+ // if we have an existing index, merge given info into it
+ if (exist != null) {
+ if (_idx != null && _idx.isUnique() && !exist.isUnique()) {
+ if (!adapt)
+ throw new MetaDataException(_loc.get(prefix
+ + "-index-not-unique", context));
+ exist.setUnique(true);
+ }
+ return exist;
+ }
+
+ // if no defaults return null
+ MappingRepository repos = (MappingRepository) context.getRepository();
+ boolean fill = repos.getMappingDefaults().defaultMissingInfo();
+ if (_idx == null && (tmplate == null || (!adapt && !fill)))
+ return null;
+
+ String name = null;
+ boolean unq;
+ if (_idx != null) {
+ name = _idx.getName();
+ unq = _idx.isUnique();
+ } else
+ unq = tmplate.isUnique();
+
+ // if no name provided by user info, make one
+ if (name == null) {
+ if (tmplate != null)
+ name = tmplate.getName();
+ else {
+ name = cols[0].getName();
+ name = repos.getDBDictionary().getValidIndexName(name, table);
+ }
+ }
+
+ Index idx = table.addIndex(name);
+ idx.setUnique(unq);
+ idx.setColumns(cols);
+ return idx;
+ }
+
+ /**
+ * Retrieve/create a unique constraint on the given columns by merging the
+ * given template information with any user-provided information.
+ *
+ * @param context the mapping we're retrieving a constraint for
+ * @param prefix localized error message key prefix
+ * @param tmplate template for expected unique information
+ * @param cols the constraint columns
+ * @param adapt whether we can modify the existing mapping or schema
+ */
+ protected Unique createUnique(MetaDataContext context, String prefix,
+ Unique tmplate, Column[] cols, boolean adapt) {
+ if (prefix == null)
+ prefix = "generic";
+
+ // can't create a constraint if there are no cols
+ if (cols == null || cols.length == 0) {
+ if (_unq != null || tmplate != null)
+ throw new MetaDataException(_loc.get(prefix
+ + "-no-unique-cols", context));
+ return null;
+ }
+
+ // look for an existing constraint on these columns
+ Table table = cols[0].getTable();
+ Unique[] unqs = table.getUniques();
+ Unique exist = null;
+ for (int i = 0; i < unqs.length; i++) {
+ if (unqs[i].columnsMatch(cols)) {
+ exist = unqs[i];
+ break;
+ }
+ }
+
+ // remove existing unique?
+ if (!_canUnq) {
+ if (exist == null)
+ return null;
+ if (!adapt)
+ throw new MetaDataException(_loc.get(prefix
+ + "-unique-exists", context));
+ table.removeUnique(exist);
+ return null;
+ }
+
+ // no defaults; return existing constraint (if any)
+ if (tmplate == null && _unq == null)
+ return exist;
+
+ MappingRepository repos = (MappingRepository) context.getRepository();
+ if (exist != null) {
+ if (_unq != null && _unq.isDeferred() && !exist.isDeferred()) {
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(_loc.get(prefix + "-defer-unique", context));
+ }
+ return exist;
+ }
+
+ // dict can't handle unique constraints?
+ DBDictionary dict = repos.getDBDictionary();
+ if (_unq != null && !dict.supportsUniqueConstraints) {
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(_loc.get(prefix + "-unique-support", context));
+ return null;
+ }
+
+ boolean fill = repos.getMappingDefaults().defaultMissingInfo();
+ if (!adapt && !fill && _unq == null)
+ return null;
+
+ String name;
+ boolean deferred;
+ if (_unq != null) {
+ name = _unq.getName();
+ deferred = _unq.isDeferred();
+ } else {
+ name = tmplate.getName();
+ deferred = tmplate.isDeferred();
+ }
+
+ if (deferred && !dict.supportsDeferredConstraints) {
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(_loc.get(prefix + "-create-defer-unique",
+ context, dict.platform));
+ deferred = false;
+ }
+
+ Unique unq = table.addUnique(name);
+ unq.setDeferred(deferred);
+ unq.setColumns(cols);
+ return unq;
+ }
+
+ /**
+ * Retrieve/create a foreign key (possibly logical) on the given columns
+ * by merging the given template information with any user-provided
+ * information.
+ *
+ * @param context the mapping we're retrieving a key for
+ * @param prefix localized error message key prefix
+ * @param given the columns given by the user
+ * @param def defaults provider
+ * @param table the table for the key
+ * @param cls type we're joining from
+ * @param rel target type we're joining to
+ * @param inversable whether the foreign key can be inversed
+ * @param adapt whether we can modify the existing mapping or schema
+ */
+ protected ForeignKey createForeignKey(MetaDataContext context,
+ String prefix, List given, ForeignKeyDefaults def, Table table,
+ ClassMapping cls, ClassMapping rel, boolean inversable, boolean adapt) {
+ assertTable(context, table);
+ if (prefix == null)
+ prefix = "generic";
+
+ // collect the foreign key columns and their targets
+ Object[][] joins = createJoins(context, prefix, table, cls, rel,
+ given, def, inversable, adapt);
+ _join = JOIN_FORWARD;
+
+ // establish local table using any join between two columns; if we only
+ // find constant joins, then keep default local table (directionless)
+ Table local = table;
+ Table foreign = rel.getTable();
+ Table tmp;
+ boolean constant = false;
+ boolean localSet = false;
+ for (int i = 0; i < joins.length; i++) {
+ if (joins[i][1]instanceof Column) {
+ tmp = ((Column) joins[i][0]).getTable();
+ if (!localSet) {
+ local = tmp;
+ localSet = true;
+ } else if (tmp != local)
+ throw new MetaDataException(_loc.get(prefix
+ + "-mult-fk-tables", context, local, tmp));
+ foreign = ((Column) joins[i][1]).getTable();
+
+ if (joins[i][2] == Boolean.TRUE)
+ _join = JOIN_INVERSE;
+ } else
+ constant = true;
+ }
+
+ // if this is not a constant join, look for existing foreign key
+ // on local columns
+ ForeignKey exist = null;
+ if (!constant && local.getForeignKeys().length > 0) {
+ Column[] cols = new Column[joins.length];
+ Column[] pks = new Column[joins.length];
+ for (int i = 0; i < joins.length; i++) {
+ cols[i] = (Column) joins[i][0];
+ pks[i] = (Column) joins[i][1];
+ }
+
+ ForeignKey[] fks = local.getForeignKeys();
+ for (int i = 0; i < fks.length; i++) {
+ if (fks[i].getConstantColumns().length == 0
+ && fks[i].getConstantPrimaryKeyColumns().length == 0
+ && fks[i].columnsMatch(cols, pks)) {
+ exist = fks[i];
+ break;
+ }
+ }
+ }
+
+ MappingRepository repos = (MappingRepository) context.getRepository();
+ DBDictionary dict = repos.getDBDictionary();
+ if (exist != null) {
+ // make existing key logical?
+ if (!_canFK) {
+ if (exist.getDeleteAction() != exist.ACTION_NONE && !adapt)
+ throw new MetaDataException(_loc.get(prefix
+ + "-fk-exists", context));
+ exist.setDeleteAction(exist.ACTION_NONE);
+ }
+
+ if (_fk != null && _fk.isDeferred() && !exist.isDeferred()) {
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(_loc.get(prefix + "-defer-fk", context));
+ }
+
+ // allow user-given info to override existing key if we're adapting;
+ // template info cannot override existing key
+ if (adapt && _fk != null) {
+ if (_fk.getUpdateAction() != ForeignKey.ACTION_NONE)
+ exist.setUpdateAction(_fk.getUpdateAction());
+ if (_fk.getDeleteAction() != ForeignKey.ACTION_NONE)
+ exist.setDeleteAction(_fk.getDeleteAction());
+ }
+ setIOFromJoins(exist, joins);
+ return exist;
+ }
+
+ String name = null;
+ int delAction = ForeignKey.ACTION_NONE;
+ int upAction = ForeignKey.ACTION_NONE;
+ boolean deferred = false;
+ boolean fill = repos.getMappingDefaults().defaultMissingInfo();
+ ForeignKey tmplate = (def == null) ? null
+ : def.get(local, foreign, _join == JOIN_INVERSE);
+ if (_fk != null && (tmplate == null || (!adapt && !fill))) {
+ // if not adapting or no template info use given data
+ name = _fk.getName();
+ delAction = _fk.getDeleteAction();
+ upAction = _fk.getUpdateAction();
+ deferred = _fk.isDeferred();
+ } else if (_canFK && (adapt || fill)) {
+ if (_fk == null && tmplate != null) {
+ // no user given info; use template data
+ name = tmplate.getName();
+ delAction = tmplate.getDeleteAction();
+ upAction = tmplate.getUpdateAction();
+ deferred = tmplate.isDeferred();
+ } else if (_fk != null && tmplate != null) {
+ // merge user and template data, always letting user info win
+ name = _fk.getName();
+ if (name == null && tmplate.getName() != null)
+ name = tmplate.getName();
+ delAction = _fk.getDeleteAction();
+ if (delAction == ForeignKey.ACTION_NONE)
+ delAction = tmplate.getDeleteAction();
+ upAction = _fk.getUpdateAction();
+ if (upAction == ForeignKey.ACTION_NONE)
+ upAction = tmplate.getUpdateAction();
+ deferred = _fk.isDeferred();
+ }
+ }
+
+ if (!dict.supportsDeleteAction(delAction)
+ || !dict.supportsUpdateAction(upAction)) {
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(_loc.get(prefix + "-unsupported-fk-action", context));
+ delAction = ForeignKey.ACTION_NONE;
+ upAction = ForeignKey.ACTION_NONE;
+ }
+ if (deferred && !dict.supportsDeferredConstraints) {
+ Log log = repos.getLog();
+ if (log.isWarnEnabled())
+ log.warn(_loc.get(prefix + "-create-defer-fk",
+ context, dict.platform));
+ deferred = false;
+ }
+
+ // create foreign key with merged info
+ ForeignKey fk = local.addForeignKey(name);
+ fk.setDeleteAction(delAction);
+ fk.setUpdateAction(upAction);
+ fk.setDeferred(deferred);
+
+ // add joins to key
+ Column col;
+ for (int i = 0; i < joins.length; i++) {
+ col = (Column) joins[i][0];
+ if (joins[i][1]instanceof Column)
+ fk.join(col, (Column) joins[i][1]);
+ else if ((joins[i][2] == Boolean.TRUE) != (_join == JOIN_INVERSE))
+ fk.joinConstant(joins[i][1], col);
+ else
+ fk.joinConstant(col, joins[i][1]);
+ }
+ setIOFromJoins(fk, joins);
+ return fk;
+ }
+
+ /**
+ * Use the join information to populate our internal column I/O data.
+ */
+ private void setIOFromJoins(ForeignKey fk, Object[][] joins) {
+ List cols = getColumns();
+ _io = null;
+ if (cols.isEmpty())
+ return;
+
+ int constIdx = 0;
+ int idx;
+ Column col;
+ for (int i = 0; i < joins.length; i++) {
+ // const columns are indexed after std join columns in fk IO
+ if (joins[i][1]instanceof Column)
+ idx = i - constIdx;
+ else if ((joins[i][2] == Boolean.TRUE) == (_join == JOIN_INVERSE))
+ idx = fk.getColumns().length + constIdx++;
+ else
+ continue;
+ setIOFromColumnFlags((Column) cols.get(i), idx);
+ }
+ }
+
+ /**
+ * Create or retrieve the foreign key joins.
+ *
+ * @param context the mapping we're retrieving a key for
+ * @param prefix localized error message key prefix
+ * @param table the table for the key
+ * @param cls type we're joining from, if applicable
+ * @param rel target type we're joining to
+ * @param given the columns given by the user
+ * @param def foreign key defaults provider
+ * @param inversable whether the foreign key can be inversed
+ * @param adapt whether we can modify the existing mapping or schema
+ * @return array of tuples where the first element is the
+ * local column (or in the case of a constant join the
+ * sole column), the second is the target column (or
+ * constant), and the third is {@link Boolean#TRUE} if
+ * this is an inverse join
+ */
+ private Object[][] createJoins(MetaDataContext context,
+ String prefix, Table table, ClassMapping cls, ClassMapping rel,
+ List given, ForeignKeyDefaults def, boolean inversable, boolean adapt) {
+ MappingRepository repos = (MappingRepository) context.getRepository();
+ boolean fill = repos.getMappingDefaults().defaultMissingInfo();
+ Object[][] joins;
+
+ // if no columns given, just create mirrors of target columns
+ if (given.isEmpty()) {
+ if (!adapt && !fill)
+ throw new MetaDataException(_loc.get(prefix + "-no-fk-cols",
+ context));
+
+ Column[] targets = rel.getPrimaryKeyColumns();
+ joins = new Object[targets.length][3];
+ Column tmplate;
+ for (int i = 0; i < targets.length; i++) {
+ tmplate = new Column();
+ tmplate.setName(targets[i].getName());
+ tmplate.setJavaType(targets[i].getJavaType());
+ tmplate.setType(targets[i].getType());
+ tmplate.setTypeName(targets[i].getTypeName());
+ tmplate.setSize(targets[i].getSize());
+ tmplate.setDecimalDigits(targets[i].getDecimalDigits());
+
+ if (def != null)
+ def.populate(table, rel.getTable(), tmplate, targets[i],
+ false, i, targets.length);
+ joins[i][0] = mergeColumn(context, prefix, tmplate, true,
+ null, table, adapt, fill);
+ joins[i][1] = targets[i];
+ }
+ return joins;
+ }
+
+ // use given columns to create join. we don't try to use any of the
+ // template columns, even if the user doesn't give a column linking to
+ // every primary key of the target type -- users are allowed to create
+ // partial joins. this means, though, that if a user wants to specify
+ // info for one join column, he has to at least create elements for
+ // all of them
+
+ joins = new Object[given.size()][3];
+ Column col;
+ for (int i = 0; i < joins.length; i++) {
+ col = (Column) given.get(i);
+ mergeJoinColumn(context, prefix, col, joins, i, table, cls, rel,
+ def, inversable && !col.getFlag(Column.FLAG_PK_JOIN), adapt,
+ fill);
+ }
+ return joins;
+ }
+
+ /**
+ * Create or retrieve a foreign key column for a join.
+ *
+ * @param context the mapping we're retrieving a key for
+ * @param prefix localized error message key prefix
+ * @param given the given local foreign key column
+ * @param joins array of joins
+ * @param idx index of the join array to populate
+ * @param table the table for the key
+ * @param cls the type we're joining from
+ * @param rel target type we're joining to
+ * @param def foreign key defaults provider;
+ * use null to mirror target column names
+ * @param inversable whether the foreign key can be inversed
+ * @param adapt whether we can modify the existing mapping or schema
+ * @param fill whether to default missing column information
+ */
+ private void mergeJoinColumn(MetaDataContext context, String prefix,
+ Column given, Object[][] joins, int idx, Table table, ClassMapping cls,
+ ClassMapping rel, ForeignKeyDefaults def, boolean inversable,
+ boolean adapt, boolean fill) {
+ // default to the primary key column name if this is a pk join
+ String name = given.getName();
+ if (name == null && given != null
+ && given.getFlag(Column.FLAG_PK_JOIN) && cls != null) {
+ Column[] pks = cls.getPrimaryKeyColumns();
+ if (pks.length == 1)
+ name = pks[0].getName();
+ }
+
+ // if we can't adapt, then the user must at least give a column name
+ if (name == null && !adapt && !fill)
+ throw new MetaDataException(_loc.get(prefix + "-no-fkcol-name",
+ context));
+
+ // check to see if the column isn't in the expected table; it might
+ // be an inverse join or a join to a base class of the target type
+ Table local = table;
+ Table foreign = rel.getTable();
+ boolean fullName = false;
+ boolean inverse = false;
+ if (name != null) {
+ int dotIdx = name.lastIndexOf('.');
+ if (dotIdx != -1) {
+ // allow use of '.' without prefix to mean "use expected
+ // foreign table"
+ if (dotIdx == 0)
+ local = foreign;
+ else
+ local = findTable(context, name.substring(0, dotIdx),
+ local, foreign, null);
+ fullName = true;
+ name = name.substring(dotIdx + 1);
+
+ // if inverse join, then swap local and foreign tables
+ if (local != table) {
+ foreign = table;
+ inverse = true;
+ }
+ }
+ }
+ boolean forceInverse = !fullName && _join == JOIN_INVERSE;
+ if (forceInverse) {
+ local = foreign;
+ foreign = table;
+ inverse = true;
+ }
+
+ // determine target
+ String targetName = given.getTarget();
+ Object target = null;
+ Table ttable = null;
+ boolean constant = false;
+ boolean fullTarget = false;
+ if (targetName == null && given.getTargetField() != null) {
+ ClassMapping tcls = (inverse) ? cls : rel;
+ String fieldName = given.getTargetField();
+ int dotIdx = fieldName.lastIndexOf('.');
+ fullTarget = dotIdx != -1;
+
+ if (dotIdx == 0) {
+ // allow use of '.' without prefix to mean "use expected local
+ // cls"; but if we already inversed no need to switch again
+ if (!inverse)
+ tcls = cls;
+ fieldName = fieldName.substring(1);
+ } else if (dotIdx > 0) {
+ // must be class + field name
+ tcls = findClassMapping(context, fieldName.substring
+ (0, dotIdx), cls, rel);
+ fieldName = fieldName.substring(dotIdx + 1);
+ }
+ if (tcls == null)
+ throw new MetaDataException(_loc.get(prefix
+ + "-bad-fktargetcls", context, fieldName, name));
+
+ FieldMapping field = tcls.getFieldMapping(fieldName);
+ if (field == null)
+ throw new MetaDataException(_loc.get(prefix
+ + "-bad-fktargetfield", new Object[]{ context, fieldName,
+ name, tcls }));
+ if (field.getColumns().length != 1)
+ throw new MetaDataException(_loc.get(prefix
+ + "-fktargetfield-cols", context, fieldName, name));
+ ttable = (field.getJoinForeignKey() != null) ? field.getTable()
+ : field.getDefiningMapping().getTable();
+ targetName = field.getColumns()[0].getName();
+ } else if (targetName != null) {
+ if (targetName.charAt(0) == '\'') {
+ constant = true;
+ target = targetName.substring(1, targetName.length() - 1);
+ } else if (targetName.charAt(0) == '-'
+ || targetName.charAt(0) == '.'
+ || Character.isDigit(targetName.charAt(0))) {
+ constant = true;
+ try {
+ if (targetName.indexOf('.') == -1)
+ target = new Integer(targetName);
+ else
+ target = new Double(targetName);
+ } catch (RuntimeException re) {
+ throw new MetaDataException(_loc.get(prefix
+ + "-bad-fkconst", context, targetName, name));
+ }
+ } else if ("null".equalsIgnoreCase(targetName))
+ constant = true;
+ else {
+ int dotIdx = targetName.lastIndexOf('.');
+ fullTarget = dotIdx != -1;
+ if (dotIdx == 0) {
+ // allow use of '.' without prefix to mean "use expected
+ // local table", but ignore if we're already inversed
+ if (!inverse)
+ ttable = local;
+ targetName = targetName.substring(1);
+ } else if (dotIdx != -1) {
+ ttable = findTable(context, targetName.substring(0,
+ dotIdx), foreign, local, (inverse) ? cls : rel);
+ targetName = targetName.substring(dotIdx + 1);
+ }
+ }
+ }
+
+ // use explicit target table if available
+ if (ttable == local && local != foreign) {
+ // swap, unless user gave incompatible table in column name
+ if (fullName)
+ throw new MetaDataException(_loc.get(prefix
+ + "-bad-fktarget-inverse", new Object[]{ context, name,
+ foreign, ttable }));
+ local = foreign;
+ foreign = ttable;
+ } else if (ttable != null) {
+ // ttable might be a table of a base class of the target
+ foreign = ttable;
+ }
+
+ // check to see if we inversed; if this is a same-table join, then
+ // consider it an implicit inverse if the user includes the table name
+ // in the column name, but not in the column target, or if the user
+ // gives no column name but a full target name
+ inverse = inverse || local != table || (local == foreign
+ && ((fullName && !fullTarget) || (name == null && fullTarget)));
+ if (!inversable && !constant && inverse) {
+ if (local == foreign)
+ throw new MetaDataException(_loc.get(prefix
+ + "-bad-fk-self-inverse", context, local));
+ throw new MetaDataException(_loc.get(prefix + "-bad-fk-inverse",
+ context, local, table));
+ }
+ if (name == null && constant)
+ throw new MetaDataException(_loc.get(prefix
+ + "-no-fkcol-name-adapt", context));
+
+ if (name == null && targetName == null) {
+ // if no name or target is provided and there's more than one likely
+ // join possibility, too ambiguous
+ PrimaryKey pk = foreign.getPrimaryKey();
+ if (joins.length != 1 || pk == null || pk.getColumns().length != 1)
+ throw new MetaDataException(_loc.get(prefix
+ + "-no-fkcol-name-adapt", context));
+
+ // assume target is pk column
+ targetName = pk.getColumns()[0].getName();
+ } else if (name != null && targetName == null) {
+ // if one primary key column use it for target; if multiple joins
+ // look for a foreign column with same name as local column
+ PrimaryKey pk = foreign.getPrimaryKey();
+ if (joins.length == 1 && pk != null && pk.getColumns().length == 1)
+ targetName = pk.getColumns()[0].getName();
+ else if (foreign.getColumn(name) != null)
+ targetName = name;
+ else
+ throw new MetaDataException(_loc.get(prefix
+ + "-no-fkcol-target-adapt", context, name));
+ }
+
+ // find the target column, and create template for local column based
+ // on it
+ Column tmplate = new Column();
+ tmplate.setName(name);
+ if (!constant) {
+ Column tcol = foreign.getColumn(targetName);
+ if (tcol == null)
+ throw new MetaDataException(_loc.get(prefix + "-bad-fktarget",
+ new Object[]{ context, targetName, name, foreign }));
+
+ if (name == null)
+ tmplate.setName(tcol.getName());
+ tmplate.setJavaType(tcol.getJavaType());
+ tmplate.setType(tcol.getType());
+ tmplate.setTypeName(tcol.getTypeName());
+ tmplate.setSize(tcol.getSize());
+ tmplate.setDecimalDigits(tcol.getDecimalDigits());
+ target = tcol;
+ } else if (target instanceof String)
+ tmplate.setJavaType(JavaTypes.STRING);
+ else if (target instanceof Integer)
+ tmplate.setJavaType(JavaTypes.INT);
+ else if (target instanceof Double)
+ tmplate.setJavaType(JavaTypes.DOUBLE);
+
+ // populate template, but let user-given name override default name
+ if (def != null)
+ def.populate(local, foreign, tmplate, target, inverse, idx,
+ joins.length);
+ if (name != null)
+ tmplate.setName(name);
+
+ // create or merge local column
+ Column col = mergeColumn(context, prefix, tmplate, true, given, local,
+ adapt, fill);
+
+ joins[idx][0] = col;
+ joins[idx][1] = target;
+ if (inverse)
+ joins[idx][2] = Boolean.TRUE;
+ }
+
+ /**
+ * Find the target class mapping given the user's class name.
+ *
+ * @param context for error messages
+ * @param clsName class name given by user
+ * @param cls original source mapping
+ * @param rel original target mapping
+ */
+ private static ClassMapping findClassMapping(MetaDataContext context,
+ String clsName, ClassMapping cls, ClassMapping rel) {
+ if (isClassMappingName(clsName, cls))
+ return cls;
+ if (isClassMappingName(clsName, rel))
+ return rel;
+ throw new MetaDataException(_loc.get("target-wrong-cls", new Object[]
+ { context, clsName, cls, rel }));
+ }
+
+ /**
+ * Return whether the given name matches the given mapping.
+ */
+ private static boolean isClassMappingName(String name, ClassMapping cls) {
+ if (cls == null)
+ return false;
+ if (name.equals(cls.getDescribedType().getName())
+ || name.equals(Strings.getClassName(cls.getDescribedType())))
+ return true;
+ return isClassMappingName(name, cls.getPCSuperclassMapping());
+ }
+
+ /**
+ * Sets internal column information to match the given mapped columns.
+ *
+ * @param forceJDBCType whether to force the jdbc-type of the columns
+ * to be set, even when it matches the default for the columns' java type
+ */
+ protected void syncColumns(MetaDataContext context, Column[] cols,
+ boolean forceJDBCType) {
+ if (cols == null || cols.length == 0)
+ _cols = null;
+ else {
+ _cols = new ArrayList(cols.length);
+ Column col;
+ for (int i = 0; i < cols.length; i++) {
+ col = syncColumn(context, cols[i], cols.length,
+ forceJDBCType, cols[i].getTable(), null, null, false);
+ setColumnFlagsFromIO(col, i);
+ _cols.add(col);
+ }
+ }
+ }
+
+ /**
+ * Set I/O flags on the column.
+ */
+ private void setColumnFlagsFromIO(Column col, int i) {
+ if (_io == null)
+ return;
+ col.setFlag(Column.FLAG_UNUPDATABLE, !_io.isUpdatable(i, false));
+ col.setFlag(Column.FLAG_UNINSERTABLE, !_io.isInsertable(i, false));
+ }
+
+ /**
+ * Sets internal index information to match given mapped index.
+ */
+ protected void syncIndex(MetaDataContext context, Index idx) {
+ if (idx == null) {
+ _idx = null;
+ return;
+ }
+
+ _canIdx = true;
+ _idx = new Index();
+ _idx.setName(idx.getName());
+ _idx.setUnique(idx.isUnique());
+ }
+
+ /**
+ * Sets internal constraint information to match given mapped constraint.
+ */
+ protected void syncUnique(MetaDataContext context, Unique unq) {
+ if (unq == null) {
+ _unq = null;
+ return;
+ }
+
+ _canUnq = true;
+ _unq = new Unique();
+ _unq.setName(unq.getName());
+ _unq.setDeferred(unq.isDeferred());
+ }
+
+ /**
+ * Sets internal constraint and column information to match given mapped
+ * constraint.
+ *
+ * @param local default local table
+ * @param target default target table
+ */
+ protected void syncForeignKey(MetaDataContext context, ForeignKey fk,
+ Table local, Table target) {
+ if (fk == null) {
+ _fk = null;
+ _cols = null;
+ _join = JOIN_NONE;
+ return;
+ }
+ if (_join == JOIN_NONE)
+ _join = JOIN_FORWARD;
+
+ if (fk.isLogical())
+ _fk = null;
+ else {
+ _canFK = true;
+ _fk = new ForeignKey();
+ _fk.setName(fk.getName());
+ _fk.setDeleteAction(fk.getDeleteAction());
+ _fk.setUpdateAction(fk.getUpdateAction());
+ _fk.setDeferred(fk.isDeferred());
+ }
+
+ Column[] cols = fk.getColumns();
+ Column[] pkCols = fk.getPrimaryKeyColumns();
+ Column[] ccols = fk.getConstantColumns();
+ Object[] cs = fk.getConstants();
+ Column[] cpkCols = fk.getConstantPrimaryKeyColumns();
+ Object[] cpks = fk.getPrimaryKeyConstants();
+
+ int size = cols.length + ccols.length + cpkCols.length;
+ _cols = new ArrayList(size);
+ Column col;
+ for (int i = 0; i < cols.length; i++) {
+ col = syncColumn(context, cols[i], size, false, local,
+ target, pkCols[i], _join == JOIN_INVERSE);
+ setColumnFlagsFromIO(col, i);
+ _cols.add(col);
+ }
+ Object constant;
+ for (int i = 0; i < ccols.length; i++) {
+ constant = (cs[i] == null) ? NULL : cs[i];
+ col = syncColumn(context, ccols[i], size, false, local,
+ target, constant, _join == JOIN_INVERSE);
+ setColumnFlagsFromIO(col, cols.length + i);
+ _cols.add(col);
+ }
+ for (int i = 0; i < cpkCols.length; i++) {
+ constant = (cpks[i] == null) ? NULL : cpks[i];
+ _cols.add(syncColumn(context, cpkCols[i], size, false, target,
+ local, constant, _join != JOIN_INVERSE));
+ }
+ }
+
+ /**
+ * Create a copy of the given column with the raw mapping information
+ * set correctly, and without settings that match defaults.
+ *
+ * @param num the number of columns for this mapping
+ * @param forceJDBCType whether the jdbc-type of the created column
+ * should be set, even if it matches the default
+ * for the given column's java type
+ * @param colTable expected table for the column
+ * @param targetTable expected target table for join column
+ * @param target target column or object for join column; for a
+ * constant null target, use {@link #NULL}
+ * @param inverse whether join column is for inverse join
+ */
+ protected static Column syncColumn(MetaDataContext context, Column col,
+ int num, boolean forceJDBCType, Table colTable, Table targetTable,
+ Object target, boolean inverse) {
+ // use full name for cols that aren't in the expected table, or that
+ // are inverse joins
+ DBDictionary dict = ((MappingRepository) context.getRepository()).
+ getDBDictionary();
+ Column copy = new Column();
+ if (col.getTable() != colTable || inverse)
+ copy.setName(dict.getFullName(col.getTable(), true)
+ + "." + col.getName());
+ else
+ copy.setName(col.getName());
+
+ // set target if not default
+ if (target != null) {
+ if (target == NULL)
+ copy.setTarget("null");
+ else if (target instanceof Column) {
+ Column tcol = (Column) target;
+ if ((!inverse && tcol.getTable() != targetTable)
+ || (inverse && tcol.getTable() != colTable))
+ copy.setTarget(dict.getFullName(tcol.getTable(), true)
+ + "." + tcol.getName());
+ else if (!defaultTarget(col, tcol, num))
+ copy.setTarget(tcol.getName());
+ } else if (target instanceof Number)
+ copy.setTarget(target.toString());
+ else
+ copy.setTarget("'" + target + "'");
+ } else if (num > 1)
+ copy.setTargetField(col.getTargetField());
+
+ if (col.getSize() != 0 && col.getSize() != dict.characterColumnSize
+ && (col.getSize() != -1 || !col.isLob()))
+ copy.setSize(col.getSize());
+ if (col.getDecimalDigits() != 0)
+ copy.setDecimalDigits(col.getDecimalDigits());
+ if (col.getDefaultString() != null)
+ copy.setDefaultString(col.getDefaultString());
+ if (col.isNotNull() && !col.isPrimaryKey()
+ && !isPrimitive(col.getJavaType()))
+ copy.setNotNull(true);
+
+ // set type name if not default
+ String typeName = col.getTypeName();
+ if (typeName != null || copy.getSize() != 0
+ || copy.getDecimalDigits() != 0) {
+ // is this the dict default type? have to ensure jdbc-type set
+ // prior to finding dict default
+ copy.setType(col.getType());
+ String defName = dict.getTypeName(copy);
+ copy.setType(Types.OTHER);
+
+ // copy should not have size info set if it isn't used in type name
+ boolean defSized = defName.indexOf('(') != -1;
+ if (!defSized) {
+ if (copy.getSize() > 0)
+ copy.setSize(0);
+ copy.setDecimalDigits(0);
+ }
+
+ if (typeName != null) {
+ // make sure to strip size for comparison
+ if (typeName.indexOf('(') == -1 && defSized)
+ defName = defName.substring(0, defName.indexOf('('));
+ if (!typeName.equalsIgnoreCase(defName))
+ copy.setTypeName(typeName);
+ }
+ }
+
+ // set jdbc-type if not default or if forced
+ if (forceJDBCType
+ || (target != null && !(target instanceof Column)
+ && col.getType() != Types.VARCHAR)
+ || dict.getJDBCType(col.getJavaType(), false) != col.getType())
+ copy.setType(col.getType());
+
+ return copy;
+ }
+
+ /**
+ * Return true if the given type code represents a primitive.
+ */
+ private static boolean isPrimitive(int type) {
+ switch (type) {
+ case JavaTypes.BOOLEAN:
+ case JavaTypes.BYTE:
+ case JavaTypes.CHAR:
+ case JavaTypes.DOUBLE:
+ case JavaTypes.FLOAT:
+ case JavaTypes.INT:
+ case JavaTypes.LONG:
+ case JavaTypes.SHORT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Return true if the given target column matches the default.
+ * If there is only one column involved in the join and it links to the
+ * single target table pk column, or if the column name is the same as
+ * the target column name, then the target is the default.
+ */
+ private static boolean defaultTarget(Column col, Column targetCol,
+ int num) {
+ if (col.getName().equals(targetCol.getName()))
+ return true;
+ if (num > 1)
+ return false;
+
+ PrimaryKey pk = targetCol.getTable().getPrimaryKey();
+ if (pk == null || pk.getColumns().length != 1)
+ return false;
+ return targetCol == pk.getColumns()[0];
+ }
+
+ /**
+ * Supplies default table information.
+ */
+ public static interface TableDefaults {
+
+ /**
+ * Return the default table name.
+ */
+ public String get(Schema schema);
+ }
+
+ /**
+ * Supplies default foreign key information.
+ */
+ public static interface ForeignKeyDefaults {
+
+ /**
+ * Return a default foreign key for the given tables, or null to
+ * create a logical foreign key only. Do not fill in the columns of
+ * the foreign key, only attributes like its name, delete action, etc.
+ * Do not add the foreign key to the table.
+ */
+ public ForeignKey get(Table local, Table foreign, boolean inverse);
+
+ /**
+ * Populate the given foreign key column with defaults.
+ *
+ * @param target the target column or constant value
+ * @param pos the index of this column in the foreign key
+ * @param cols the number of columns in the foreign key
+ */
+ public void populate(Table local, Table foreign, Column col,
+ Object target, boolean inverse, int pos, int cols);
+ }
+}
Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingInfo.java
------------------------------------------------------------------------------
svn:executable = *