blob: 333a4a957e4207832b6976fe14fce0505381cb0e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jdbc.meta;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.QualifiedDBIdentifier;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.xml.Commentable;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataContext;
import org.apache.openjpa.util.UserException;
/**
* Information about the mapping from a class to the schema, in raw form.
* The columns and tables used in mapping info will not be part of the
* {@link SchemaGroup} used at runtime. Rather, they will be structs
* with the relevant pieces of information filled in.
*
* @author Abe White
*/
public class ClassMappingInfo
extends MappingInfo
implements SourceTracker, Commentable {
private static final long serialVersionUID = 1L;
private static final Localizer _loc = Localizer.forPackage
(ClassMappingInfo.class);
private String _className = Object.class.getName();
private DBIdentifier _tableName = DBIdentifier.NULL;
private DBIdentifier _schemaName = DBIdentifier.NULL;
private boolean _joined = false;
private Map<DBIdentifier, List<Column>> _seconds = null;
private String _subStrat = null;
private File _file = null;
private int _srcType = SRC_OTHER;
private String[] _comments = null;
private int _lineNum = 0;
private int _colNum = 0;
// Unique constraints indexed by primary or secondary table name
private Map<DBIdentifier,List<Unique>> _uniques;
private Map<DBIdentifier,List<Index>> _indices = new HashMap<>();
/**
* The described class name.
*/
public String getClassName() {
return _className;
}
/**
* The described class name.
*/
public void setClassName(String name) {
_className = name;
}
/**
* The default strategy for subclasses in this hierarchy.
*/
public String getHierarchyStrategy() {
return _subStrat;
}
/**
* The default strategy for subclasses in this hierarchy.
*/
public void setHierarchyStrategy(String strategy) {
_subStrat = strategy;
}
/**
* The given table name.
* @deprecated
*/
@Deprecated
public String getTableName() {
return getTableIdentifier().getName();
}
public DBIdentifier getTableIdentifier() {
return _tableName == null ? DBIdentifier.NULL : _tableName;
}
/**
* The given table name.
* @deprecated
*/
@Deprecated
public void setTableName(String table) {
setTableIdentifier(DBIdentifier.newTable(table));
}
public void setTableIdentifier(DBIdentifier table) {
_tableName = table;
}
/**
* The default schema name for unqualified tables.
* @deprecated
*/
@Deprecated
public String getSchemaName() {
return getSchemaIdentifier().getName();
}
public DBIdentifier getSchemaIdentifier() {
return _schemaName == null ? DBIdentifier.NULL : _schemaName;
}
/**
* The default schema name for unqualified tables.
* @deprecated
*/
@Deprecated
public void setSchemaName(String schema) {
setSchemaIdentifier(DBIdentifier.newSchema(schema));
}
public void setSchemaIdentifier(DBIdentifier schema) {
_schemaName = schema;
}
/**
* Whether there is a join to the superclass table.
*/
public boolean isJoinedSubclass() {
return _joined;
}
/**
* Whether there is a join to the superclass table.
*/
public void setJoinedSubclass(boolean joined) {
_joined = joined;
}
/**
* Return the class-level joined tables.
* @deprecated
*/
@Deprecated
public String[] getSecondaryTableNames() {
if (_seconds == null)
return new String[0];
return DBIdentifier.toStringArray(_seconds.keySet().toArray(new DBIdentifier[]{ }));
}
public DBIdentifier[] getSecondaryTableIdentifiers() {
if (_seconds == null)
return new DBIdentifier[0];
return _seconds.keySet().toArray(new DBIdentifier[_seconds.size()]);
}
/**
* We allow fields to reference class-level joins using just the table
* name, whereas the class join might have schema, etc information.
* This method returns the name of the given table as listed in a
* class-level join, or the given name if no join exists.
* @deprecated
*/
@Deprecated
public String getSecondaryTableName(String tableName) {
return getSecondaryTableIdentifier(DBIdentifier.newTable(tableName)).getName();
}
public DBIdentifier getSecondaryTableIdentifier(DBIdentifier tableName) {
// if no secondary table joins, bad table name, exact match,
// or an already-qualified table name, nothing to do
QualifiedDBIdentifier path = QualifiedDBIdentifier.getPath(tableName);
if (_seconds == null || DBIdentifier.isNull(tableName)
|| _seconds.containsKey(tableName)
|| !DBIdentifier.isNull(path.getSchemaName()))
return tableName;
// decide which class-level join table is best match
DBIdentifier best = tableName;
int pts = 0;
DBIdentifier fullJoin = DBIdentifier.NULL;
DBIdentifier join = DBIdentifier.NULL;
for (DBIdentifier dbIdentifier : _seconds.keySet()) {
// award a caseless match without schema 2 points
fullJoin = dbIdentifier;
QualifiedDBIdentifier joinPath = QualifiedDBIdentifier.getPath(fullJoin);
if (joinPath.isUnqualifiedObject() && pts < 2 && fullJoin.equalsIgnoreCase(tableName)) {
best = fullJoin;
pts = 2;
}
else if (joinPath.isUnqualifiedObject())
continue;
// immediately return an exact match with schema
join = joinPath.getIdentifier();
if (join.equals(tableName))
return fullJoin;
// caseless match with schema worth 1 point
if (pts < 1 && join.equalsIgnoreCase(tableName)) {
best = fullJoin;
pts = 1;
}
}
return best;
}
/**
* Return any columns defined for the given class level join, or empty
* list if none.
* @deprecated
*/
@Deprecated
public List<Column> getSecondaryTableJoinColumns(String tableName) {
return getSecondaryTableJoinColumns(DBIdentifier.newTable(tableName));
}
public List<Column> getSecondaryTableJoinColumns(DBIdentifier tableName) {
if (_seconds == null || DBIdentifier.isNull(tableName)) {
return Collections.emptyList();
}
// get the columns for the join with the best match for table name
List<Column> cols = _seconds.get(getSecondaryTableIdentifier(tableName));
if (cols == null) {
// possible that given table has extra info the join table
// doesn't have; strip it
QualifiedDBIdentifier path = QualifiedDBIdentifier.getPath(tableName);
if (!DBIdentifier.isNull(path.getSchemaName())) {
tableName = path.getIdentifier();
cols = _seconds.get(getSecondaryTableIdentifier(tableName));
}
}
if (cols == null) {
return Collections.emptyList();
}
return cols;
}
/**
* Adds a Secondary table of given name to this mapping. A secondary table
* must be known before unique constraints are added to a Secondary table.
* @deprecated
*/
@Deprecated
public void addSecondaryTable(String second) {
setSecondaryTableJoinColumns(DBIdentifier.newTable(second), null);
}
public void addSecondaryTable(DBIdentifier second) {
setSecondaryTableJoinColumns(second, null);
}
/**
* Declare the given class-level join to the named (secondary) table.
* @deprecated
*/
@Deprecated
public void setSecondaryTableJoinColumns(String tableName, List<Column> cols) {
if (cols == null)
cols = Collections.emptyList();
setSecondaryTableJoinColumns(DBIdentifier.newTable(tableName), cols);
}
public void setSecondaryTableJoinColumns(DBIdentifier tableName, List<Column> cols) {
if (cols == null)
cols = Collections.emptyList();
if (_seconds == null)
_seconds = new LinkedHashMap<>();
_seconds.put(tableName, cols);
}
/**
* Return the named table for the given class.
* @deprecated
*/
@Deprecated
public Table getTable(final ClassMapping cls, String tableName,
boolean adapt) {
return getTable(cls, DBIdentifier.newTable(tableName), adapt);
}
/**
* Return the named table for the given class.
*/
public Table getTable(final ClassMapping cls, DBIdentifier tableName,
boolean adapt) {
Table t = createTable(cls, new TableDefaults() {
@Override
public String get(Schema schema) {
// delay this so that we don't do schema reflection for unique
// table name unless necessary
return cls.getMappingRepository().getMappingDefaults().
getTableName(cls, schema);
}
@Override
public DBIdentifier getIdentifier(Schema schema) {
return cls.getMappingRepository().getMappingDefaults().
getTableIdentifier(cls, schema);
}
}, _schemaName, tableName, adapt);
t.setComment(cls.getTypeAlias() == null
? cls.getDescribedType().getName()
: cls.getTypeAlias());
return t;
}
/**
* Return the primary table for the given class.
*/
public Table getTable(final ClassMapping cls, boolean adapt) {
return getTable(cls, _tableName, adapt);
}
/**
* Return the datastore identity columns for the given class, based on the
* given templates.
*/
public Column[] getDataStoreIdColumns(ClassMapping cls, Column[] tmplates,
Table table, boolean adapt) {
cls.getMappingRepository().getMappingDefaults().
populateDataStoreIdColumns(cls, table, tmplates);
return createColumns(cls, "datastoreid", tmplates, table, adapt);
}
/**
* Return the join from this class to its superclass. The table for
* this class must be set.
*/
public ForeignKey getSuperclassJoin(final ClassMapping cls, Table table,
boolean adapt) {
ClassMapping sup = cls.getJoinablePCSuperclassMapping();
if (sup == null)
return null;
ForeignKeyDefaults def = new ForeignKeyDefaults() {
@Override
public ForeignKey get(Table local, Table foreign, boolean inverse) {
return cls.getMappingRepository().getMappingDefaults().
getJoinForeignKey(cls, local, foreign);
}
@Override
public void populate(Table local, Table foreign, Column col,
Object target, boolean inverse, int pos, int cols) {
cls.getMappingRepository().getMappingDefaults().
populateJoinColumn(cls, local, foreign, col, target,
pos, cols);
}
};
return createForeignKey(cls, "superclass", getColumns(), def, table,
cls, sup, false, adapt);
}
/**
* Synchronize internal information with the mapping data for the given
* class.
*/
public void syncWith(ClassMapping cls) {
clear(false);
ClassMapping sup = cls.getMappedPCSuperclassMapping();
if (cls.getTable() != null && (sup == null
|| sup.getTable() != cls.getTable()))
_tableName = cls.getMappingRepository().getDBDictionary().
getFullIdentifier(cls.getTable(), true);
// set io before syncing cols
setColumnIO(cls.getColumnIO());
if (cls.getJoinForeignKey() != null && sup != null
&& sup.getTable() != null)
syncForeignKey(cls, cls.getJoinForeignKey(), cls.getTable(),
sup.getTable());
else if (cls.getIdentityType() == ClassMetaData.ID_DATASTORE)
syncColumns(cls, cls.getPrimaryKeyColumns(), false);
// record inheritance strategy if class does not use default strategy
// for base classes, and for all subclasses so we can be sure subsequent
// mapping runs don't think subclass is unmapped
String strat = (cls.getStrategy() == null) ? null
: cls.getStrategy().getAlias();
if (strat != null && (cls.getPCSuperclass() != null
|| !FullClassStrategy.ALIAS.equals(strat)))
setStrategy(strat);
}
@Override
public boolean hasSchemaComponents() {
return super.hasSchemaComponents() || !DBIdentifier.isNull(_tableName);
}
@Override
protected void clear(boolean canFlags) {
super.clear(canFlags);
_tableName = DBIdentifier.NULL;
}
@Override
public void copy(MappingInfo info) {
super.copy(info);
if (!(info instanceof ClassMappingInfo))
return;
ClassMappingInfo cinfo = (ClassMappingInfo) info;
if (DBIdentifier.isNull(_tableName))
_tableName = cinfo.getTableIdentifier();
if (_subStrat == null)
_subStrat = cinfo.getHierarchyStrategy();
if (cinfo._seconds != null) {
if (_seconds == null)
_seconds = new HashMap<>();
DBIdentifier key;
for (DBIdentifier dbIdentifier : cinfo._seconds.keySet()) {
key = dbIdentifier;
if (!_seconds.containsKey(key))
_seconds.put(key, cinfo._seconds.get(key));
}
}
if (cinfo._uniques != null) {
if (_uniques == null) {
_uniques = new HashMap<>();
}
for (Entry<DBIdentifier, List<Unique>> entry : cinfo._uniques.entrySet()) {
if (!_uniques.containsKey(entry.getKey())) {
_uniques.put(entry.getKey(), entry.getValue());
}
}
}
_indices.clear();
for (Entry<DBIdentifier, List<Index>> entry : cinfo._indices.entrySet()) {
if (!_indices.containsKey(entry.getKey())) {
_indices.put(entry.getKey(), entry.getValue());
}
}
}
/**
* Add a unique constraint for the given table.
* @param table must be primary table or secondary table name added a
* priori to this receiver.
* @param unique the unique constraint. null means no-op.
* @deprecated
*/
@Deprecated
public void addUnique(String table, Unique unique) {
addUnique(DBIdentifier.newTable(table), unique);
}
/**
* Add a unique constraint for the given table.
* @param table must be primary table or secondary table name added a
* priori to this receiver.
* @param unique the unique constraint. null means no-op.
*/
public void addUnique(DBIdentifier table, Unique unique) {
if (!DBIdentifier.equal(_tableName, table) &&
(_seconds == null || !_seconds.containsKey(table))) {
throw new UserException(_loc.get("unique-no-table",
new Object[]{table, _className, _tableName,
((_seconds == null) ? "" : _seconds.keySet())}));
}
if (unique == null)
return;
if (_uniques == null)
_uniques = new HashMap<>();
unique.setTableIdentifier(table);
List<Unique> uniques = _uniques.get(table);
if (uniques == null) {
uniques = new ArrayList<>();
uniques.add(unique);
_uniques.put(table, uniques);
} else {
uniques.add(unique);
}
}
/**
* Add index for the given table.
* @param table must be primary table or secondary table name added a
* priori to this receiver.
* @param idx the index. null means no-op.
*/
public void addIndex(DBIdentifier table, Index idx) {
if (!DBIdentifier.equal(_tableName, table) &&
(_seconds == null || !_seconds.containsKey(table))) {
throw new UserException(_loc.get("index-no-table",
new Object[]{table, _className, _tableName,
((_seconds == null) ? "" : _seconds.keySet())}));
}
if (idx == null)
return;
idx.setTableIdentifier(table);
List<Index> indices = _indices.get(table);
if (indices == null) {
indices = new ArrayList<>();
indices.add(idx);
_indices.put(table, indices);
} else {
indices.add(idx);
}
}
/**
* Get the unique constraints of the given primary or secondary table.
* @deprecated
*/
@Deprecated
public Unique[] getUniques(String table) {
return getUniques(DBIdentifier.newTable(table));
}
/**
* Get the unique constraints of the given primary or secondary table.
*/
public Unique[] getUniques(DBIdentifier table) {
if (_uniques == null || _uniques.isEmpty()
|| _uniques.containsKey(table))
return new Unique[0];
List<Unique> uniques = _uniques.get(table);
return uniques.toArray(new Unique[uniques.size()]);
}
/**
* Get all the unique constraints associated with both the primary and/or
* secondary tables.
*
*/
public Unique[] getUniques(MetaDataContext cm, boolean adapt) {
if (_uniques == null || _uniques.isEmpty())
return new Unique[0];
List<Unique> result = new ArrayList<>();
for (DBIdentifier tableName : _uniques.keySet()) {
List<Unique> uniqueConstraints = _uniques.get(tableName);
for (Unique template : uniqueConstraints) {
Column[] templateColumns = template.getColumns();
Column[] uniqueColumns = new Column[templateColumns.length];
Table table = getTable((ClassMapping)cm, tableName, adapt);
for (int i=0; i<uniqueColumns.length; i++) {
DBIdentifier columnName = templateColumns[i].getIdentifier();
if (!table.containsColumn(columnName)) {
throw new UserException(_loc.get(
"unique-missing-column",
new Object[]{cm, columnName, tableName,
Arrays.toString(table.getColumnNames())}));
}
Column uniqueColumn = table.getColumn(columnName);
uniqueColumns[i] = uniqueColumn;
}
Unique unique = createUnique(cm, "unique", template,
uniqueColumns, adapt);
if (unique != null)
result.add(unique);
}
}
return result.toArray(new Unique[result.size()]);
}
/**
* Get all indices associated with both the primary and/or
* secondary tables.
*
*/
public Index[] getIndices(MetaDataContext cm, boolean adapt) {
if (_indices.isEmpty())
return new Index[0];
List<Index> result = new ArrayList<>();
for (DBIdentifier tableName : _indices.keySet()) {
List<Index> indices = _indices.get(tableName);
for (Index template : indices) {
Column[] templateColumns = template.getColumns();
Column[] columns = new Column[templateColumns.length];
Table table = getTable((ClassMapping)cm, tableName, adapt);
for (int i = 0; i < columns.length; i++) {
DBIdentifier columnName = templateColumns[i].getIdentifier();
if (!table.containsColumn(columnName)) {
throw new UserException(_loc.get(
"index-missing-column",
new Object[]{cm, columnName, tableName,
Arrays.toString(table.getColumnNames())}));
}
Column column = table.getColumn(columnName);
columns[i] = column;
}
Index idx = createIndex(cm, "index", template, columns, adapt);
if (idx != null)
result.add(idx);
}
}
return result.toArray(new Index[result.size()]);
}
@Override
public File getSourceFile() {
return _file;
}
@Override
public Object getSourceScope() {
return null;
}
@Override
public int getSourceType() {
return _srcType;
}
public void setSource(File file, int srcType) {
_file = file;
_srcType = srcType;
}
@Override
public String getResourceName() {
return _className;
}
@Override
public String[] getComments() {
return (_comments == null) ? EMPTY_COMMENTS : _comments;
}
@Override
public void setComments(String[] comments) {
_comments = comments;
}
@Override
public int getLineNumber() {
return _lineNum;
}
public void setLineNumber(int lineNum) {
_lineNum = lineNum;
}
@Override
public int getColNumber() {
return _colNum;
}
public void setColNumber(int colNum) {
_colNum = colNum;
}
}