blob: d1699ea4979faaeafc14fe504d7a2f266fc53e0d [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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
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.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.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 Localizer _loc = Localizer.forPackage
(ClassMappingInfo.class);
private String _className = Object.class.getName();
private String _tableName = null;
private String _schemaName = null;
private boolean _joined = false;
private Map _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<String,List<Unique>> _uniques;
/**
* 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.
*/
public String getTableName() {
return _tableName;
}
/**
* The given table name.
*/
public void setTableName(String table) {
_tableName = table;
}
/**
* The default schema name for unqualified tables.
*/
public String getSchemaName() {
return _schemaName;
}
/**
* The default schema name for unqualified tables.
*/
public void setSchemaName(String 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.
*/
public String[] getSecondaryTableNames() {
if (_seconds == null)
return new String[0];
return (String[]) _seconds.keySet().toArray(new String[]{ });
}
/**
* 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.
*/
public String getSecondaryTableName(String tableName) {
// if no secondary table joins, bad table name, exact match,
// or an already-qualified table name, nothing to do
if (_seconds == null || tableName == null
|| _seconds.containsKey(tableName)
|| tableName.indexOf('.') != -1)
return tableName;
// decide which class-level join table is best match
String best = tableName;
int pts = 0;
String fullJoin;
String join;
int idx;
for (Iterator itr = _seconds.keySet().iterator(); itr.hasNext();) {
// award a caseless match without schema 2 points
fullJoin = (String) itr.next();
idx = fullJoin.lastIndexOf('.');
if (idx == -1 && pts < 2 && fullJoin.equalsIgnoreCase(tableName)) {
best = fullJoin;
pts = 2;
} else if (idx == -1)
continue;
// immediately return an exact match with schema
join = fullJoin.substring(idx + 1);
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.
*/
public List getSecondaryTableJoinColumns(String tableName) {
if (_seconds == null || tableName == null)
return Collections.EMPTY_LIST;
// get the columns for the join with the best match for table name
List cols = (List) _seconds.get(getSecondaryTableName(tableName));
if (cols == null) {
// possible that given table has extra info the join table
// doesn't have; strip it
int idx = tableName.lastIndexOf('.');
if (idx != -1) {
tableName = tableName.substring(idx + 1);
cols = (List) _seconds.get(getSecondaryTableName(tableName));
}
}
return (cols == null) ? Collections.EMPTY_LIST : 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.
*/
public void addSecondaryTable(String second) {
setSecondaryTableJoinColumns(second, null);
}
/**
* Declare the given class-level join to the named (secondary) table.
*/
public void setSecondaryTableJoinColumns(String tableName, List cols) {
if (cols == null)
cols = Collections.EMPTY_LIST;
if (_seconds == null)
_seconds = new HashMap();
_seconds.put(tableName, cols);
}
/**
* Return the named table for the given class.
*/
public Table getTable(final ClassMapping cls, String tableName,
boolean adapt) {
Table t = createTable(cls, new TableDefaults() {
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);
}
}, _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() {
public ForeignKey get(Table local, Table foreign, boolean inverse) {
return cls.getMappingRepository().getMappingDefaults().
getJoinForeignKey(cls, local, foreign);
}
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().
getFullName(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() == ClassMapping.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);
}
public boolean hasSchemaComponents() {
return super.hasSchemaComponents() || _tableName != null;
}
protected void clear(boolean canFlags) {
super.clear(canFlags);
_tableName = null;
}
public void copy(MappingInfo info) {
super.copy(info);
if (!(info instanceof ClassMappingInfo))
return;
ClassMappingInfo cinfo = (ClassMappingInfo) info;
if (_tableName == null)
_tableName = cinfo.getTableName();
if (_subStrat == null)
_subStrat = cinfo.getHierarchyStrategy();
if (cinfo._seconds != null) {
if (_seconds == null)
_seconds = new HashMap();
Object key;
for (Iterator itr = cinfo._seconds.keySet().iterator();
itr.hasNext();) {
key = itr.next();
if (!_seconds.containsKey(key))
_seconds.put(key, cinfo._seconds.get(key));
}
}
if (cinfo._uniques != null) {
if (_uniques == null)
_uniques = new HashMap<String, List<Unique>>();
for (Entry<String, List<Unique>> entry : cinfo._uniques.entrySet())
if (!_uniques.containsKey(entry.getKey()))
_uniques.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.
*/
public void addUnique(String table, Unique unique) {
if (!StringUtils.equals(_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<String,List<Unique>>();
unique.setTableName(table);
List<Unique> uniques = _uniques.get(table);
if (uniques == null) {
uniques = new ArrayList<Unique>();
uniques.add(unique);
_uniques.put(table, uniques);
} else {
uniques.add(unique);
}
}
/**
* Get the unique constraints of the given primary or secondary table.
*/
public Unique[] getUniques(String 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<Unique>();
for (String 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++) {
String columnName = templateColumns[i].getName();
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()]);
}
public File getSourceFile() {
return _file;
}
public Object getSourceScope() {
return null;
}
public int getSourceType() {
return _srcType;
}
public void setSource(File file, int srcType) {
_file = file;
_srcType = srcType;
}
public String getResourceName() {
return _className;
}
public String[] getComments() {
return (_comments == null) ? EMPTY_COMMENTS : _comments;
}
public void setComments(String[] comments) {
_comments = comments;
}
public int getLineNumber() {
return _lineNum;
}
public void setLineNumber(int lineNum) {
_lineNum = lineNum;
}
public int getColNumber() {
return _colNum;
}
public void setColNumber(int colNum) {
_colNum = colNum;
}
}