/* * Copyright 2010 Dennis Butterstein, Ralf Joachim * * 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. * * $Id: TableInfo.java 8469 2009-12-28 16:47:54Z rjoachim $ */ package org.castor.cpa.persistence.sql.engine.info; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import javax.persistence.PersistenceException; import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature; import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.loader.ClassDescriptorImpl; import org.exolab.castor.mapping.loader.FieldHandlerImpl; import org.exolab.castor.persist.spi.Identity; /** * Class representing given table classes as Tables. * * @author <a href="mailto:madsheepscarer AT googlemail DOT com">Dennis Butterstein</a> * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a> * @version $Revision: 8469 $ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ */ public final class TableInfo { //----------------------------------------------------------------------------------- /** Variable storing name of the table. */ private String _tableName; /** Variable storing the table extended by this one. */ private TableInfo _extendedTable; /** List of tables that are extending this one. */ private List<TableInfo> _extendingTables = new ArrayList<TableInfo>(); /** ClassDescriptor of this table. */ private ClassDescriptor _clsDesc; /** List of primary key columns. */ private ArrayList<ColInfo> _pkColumns = new ArrayList<ColInfo>(); /** List of normal columns with no special meaning. */ private ArrayList<ColInfo> _columns = new ArrayList<ColInfo>(); /** List of foreign keys consisting of TableLinks. */ private ArrayList<TableLink> _fks = new ArrayList<TableLink>(); /** Index to map columns back to fields. */ private int _fldIndex = 0; //----------------------------------------------------------------------------------- /** * Constructor taking tableName in order to construct Table that holds his name only. * * @param tableName Name of the table to be constructed. */ private TableInfo(final String tableName) { _tableName = tableName; } /** * Constructor that starts building TableInfo hierarchy recursively. * * @param clsDesc ClassDescriptor this TableInfo belongs to. * @param tblMap Map of TableInfo instances already created. * @throws MappingException Exception thrown when errors occur. */ public TableInfo(final ClassDescriptor clsDesc, final HashMap<ClassDescriptor, TableInfo> tblMap) throws MappingException { _clsDesc = clsDesc; if (!_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) { throw new MappingException("ClassDescriptor has not a JDOClassDescriptor"); } _tableName = new ClassDescriptorJDONature(_clsDesc).getTableName(); if (tblMap == null) { throw new MappingException("Table map was not initialized!"); } tblMap.put(_clsDesc, this); String fldDescJdoNatureName = FieldDescriptorJDONature.class.getName(); // add extended table to tableInfo if exists. if (_clsDesc.getExtends() != null) { _extendedTable = getTableInfo(_clsDesc.getExtends(), tblMap); } // add extending tables to known resources. for (ClassDescriptor desc : new ClassDescriptorJDONature(_clsDesc).getExtended()) { _extendingTables.add(getTableInfo(desc, tblMap)); } // first we have to add the primary keys of this class. FieldDescriptor[] ids = ((ClassDescriptorImpl) getClassDescriptor()).getIdentities(); for (int i = 0; i < ids.length; i++) { if (!ids[i].hasNature(fldDescJdoNatureName)) { throw new MappingException("Except JDOFieldDescriptor"); } String[] sqlName = new FieldDescriptorJDONature(ids[i]).getSQLName(); int[] sqlType = new FieldDescriptorJDONature(ids[i]).getSQLType(); FieldHandlerImpl fh = (FieldHandlerImpl) ids[i].getHandler(); addPkColumn(new ColInfo(sqlName[0], sqlType[0], fh.getConvertTo(), fh.getConvertFrom(), false, -1, false)); } // then we have to add the other columns, such as foreign keys and normal columns. FieldDescriptor[] fieldDscs = getClassDescriptor().getFields(); for (int i = 0; i < fieldDscs.length; i++) { // fieldDescriptors[i] is persistent in db if it is not transient // and it has a JDOFieldDescriptor or has a ClassDescriptor if (fieldDscs[i].isTransient() || !(fieldDscs[i].hasNature(fldDescJdoNatureName)) && (fieldDscs[i].getClassDescriptor() == null)) { continue; } ClassDescriptor related = fieldDscs[i].getClassDescriptor(); if (related != null) { if (!(related.hasNature(ClassDescriptorJDONature.class.getName()))) { throw new MappingException("Related class is not JDOClassDescriptor"); } FieldDescriptor[] relids = ((ClassDescriptorImpl) related).getIdentities(); String[] names = constructIdNames(relids); FieldDescriptor[] classids = ((ClassDescriptorImpl) _clsDesc).getIdentities(); String[] classnames = constructIdNames(classids); if (!(fieldDscs[i].hasNature(fldDescJdoNatureName))) { // needed when there are fields without given columns (test 89) TableInfo table = getTableInfo(related, tblMap); TableLink tblLnk = new TableLink(table, TableLink.MANY_KEY, table.getTableName() + "_f" + i, _pkColumns, _fldIndex); _fks.add(tblLnk); tblLnk.setManyKey(Arrays.asList(classnames)); } else { FieldDescriptorJDONature jdoFieldNature = new FieldDescriptorJDONature(fieldDscs[i]); String[] tempNames = jdoFieldNature.getSQLName(); if ((tempNames != null) && (tempNames.length != relids.length)) { throw new MappingException("The number of columns of foreign key " + "does not match with what specified in manyKey"); } names = (tempNames != null) ? tempNames : names; String[] joinFields = jdoFieldNature.getManyKey(); if ((joinFields != null) && (joinFields.length != classnames.length)) { throw new MappingException("The number of columns of foreign key " + "does not match with what specified in manyKey"); } joinFields = (joinFields != null) ? joinFields : classnames; if (jdoFieldNature.getManyTable() != null) { TableInfo table = new TableInfo(jdoFieldNature.getManyTable()); TableLink tblLnk = new TableLink(table, TableLink.MANY_TABLE, table.getTableName() + "_f" + i, _pkColumns, _fldIndex); _fks.add(tblLnk); // add normal columns for (String name : Arrays.asList(names)) { table.addCol(new ColInfo(name)); } // add target columns for (String join : Arrays.asList(joinFields)) { tblLnk.addTargetCol(new ColInfo(join)); } } else if (jdoFieldNature.getSQLName() != null) { // 1:1 relation boolean store = (_extendedTable == null) && !jdoFieldNature.isReadonly(); ArrayList<ColInfo> columns = new ArrayList<ColInfo>(); for (int j = 0; j < relids.length; j++) { if (!(relids[j].hasNature(fldDescJdoNatureName))) { throw new MappingException("Related class identities field does" + " not contains sql information!"); } FieldDescriptor relId = relids[j]; FieldHandlerImpl fh = (FieldHandlerImpl) relId.getHandler(); columns.add(new ColInfo(names[j], new FieldDescriptorJDONature(relId).getSQLType()[0], fh.getConvertTo(), fh.getConvertFrom(), store, _fldIndex, jdoFieldNature.isDirtyCheck())); } TableInfo table = getTableInfo(related, tblMap); TableLink tblLnk = new TableLink(table, TableLink.SIMPLE, table.getTableName() + "_f" + i, columns, _fldIndex); _fks.add(tblLnk); tblLnk.addTargetCols(table.getPkColumns()); } else { TableInfo table = getTableInfo(related, tblMap); TableLink tblLnk = new TableLink(table, TableLink.MANY_KEY, table.getTableName() + "_f" + i, _pkColumns, _fldIndex); _fks.add(tblLnk); tblLnk.setManyKey(Arrays.asList(joinFields)); } } } else { FieldDescriptorJDONature jdoFieldNature = new FieldDescriptorJDONature(fieldDscs[i]); boolean store = (_extendedTable == null) && !jdoFieldNature.isReadonly(); boolean dirtyCheck = jdoFieldNature.isDirtyCheck(); String sqlName = fieldDscs[i].getFieldName(); if (jdoFieldNature.getSQLName() != null) { sqlName = jdoFieldNature.getSQLName()[0]; } FieldHandlerImpl fh = (FieldHandlerImpl) fieldDscs[i].getHandler(); addCol(new ColInfo (sqlName, jdoFieldNature.getSQLType()[0], fh.getConvertTo(), fh.getConvertFrom(), store, _fldIndex, dirtyCheck)); } _fldIndex++; } } //----------------------------------------------------------------------------------- /** * Method checks if table for given classDescriptor exists. If there is one it will be returned * otherwise a new Table will be constructed for this classDescriptor. * * @param clsDesc ClassDescriptor to search table for. * @param tblMap Map holding classDescriptors with corresponding tables. * @return Existing table from the map or a new one. * @throws MappingException Error thrown when construction of new table fails. */ private TableInfo getTableInfo(final ClassDescriptor clsDesc, final HashMap<ClassDescriptor, TableInfo> tblMap) throws MappingException { if (tblMap.containsKey(clsDesc)) { return tblMap.get(clsDesc); } return new TableInfo(clsDesc, tblMap); } /** * Method constructing array of the sqlNames of the belonging columns. * * @param fieldDesc FieldDescriptor to get names from. * @return Array of the names of the columns. * @throws MappingException If an error occurs. */ private String[] constructIdNames(final FieldDescriptor[] fieldDesc) throws MappingException { String[] names = new String[fieldDesc.length]; for (int j = 0; j < fieldDesc.length; j++) { names[j] = new FieldDescriptorJDONature(fieldDesc[j]).getSQLName()[0]; if (names[j] == null) { throw new MappingException("Related class identities field does " + "not contain sql information!"); } } return names; } /** * Method adjusting tableLinks in the correct manner. This method has to be executed * right after the complete table-hierarchy has been built. * It is needed because in some cases during construction of the hierarchy not all needed * columns will be already created when needed. */ public void adjustTableLinks() { for (TableLink tblLnk : _fks) { if (TableLink.MANY_KEY == tblLnk.getRelationType()) { for (ColInfo col : tblLnk.getTargetTable().iterateAll()) { for (String key : tblLnk.getManyKey()) { if (key.equals(col.getName())) { tblLnk.addTargetCol(col); } } } if (tblLnk.getTargetCols().isEmpty()) { for (TableLink tblLink : tblLnk.getTargetTable().getFkColumns()) { if (tblLink.getTargetTable().equals(this) && tblLink.getManyKey() != null) { for (ColInfo col : tblLink.getStartCols()) { for (String key : tblLink.getManyKey()) { if (key.equals(col.getName())) { tblLnk.addTargetCols(tblLink.getStartCols()); } } } } } } // needed when many key exists in a table but there is no reference specified // in the target-table pointing back (test 2996) if (tblLnk.getTargetCols().isEmpty()) { for (String key : tblLnk.getManyKey()) { ColInfo col = new ColInfo(key); tblLnk.getTargetTable().addCol(col); tblLnk.addTargetCol(col); } } } } } /** * Method returning list of all columns belonging to this table. * * @return List of collected columns. */ public List<ColInfo> iterateAll() { List<ColInfo> cols = new ArrayList<ColInfo>(); cols.addAll(_pkColumns); cols.addAll(_columns); for (TableLink lnk : _fks) { for (ColInfo col : lnk.getStartCols()) { if (!cols.contains(col)) { cols.add(col); } } } return cols; } /** * Method appending values from passed identity to corresponding columns. * * @param input Identity containing values to be assigned to corresponding columns. * @return ArrayList containing all columns with their corresponding values. */ public ArrayList<ColInfo> toSQL(final Identity input) { ArrayList<ColInfo> pksWithValues = new ArrayList<ColInfo>(); for (int i = 0; i < _pkColumns.size(); i++) { ColInfo column = new ColInfo(_pkColumns.get(i)); column.setValue(input.get(i)); pksWithValues.add(column); } return pksWithValues; } /** * Method appending values from passed identity to corresponding columns. * * @param input Identity containing values to be assigned to corresponding columns. * @return ArrayList containing all columns with their corresponding values. */ public List<ColInfo> toSQL(final Object[] input) { List<ColInfo> colsWithValues = new ArrayList<ColInfo>(); for (int i = 0; i < _columns.size(); i++) { ColInfo column = new ColInfo(_columns.get(i)); colsWithValues.add(column); } for (TableLink lnk : _fks) { for (ColInfo col : lnk.getStartCols()) { if (!colsWithValues.contains(col)) { if (col.getFieldIndex() == -1) { // index of foreign key columns has to be taken from tableLink // because the the fields in this case have to use other fieldindexes // than in their tables. col.setFieldIndex(lnk.getFieldIndex()); } colsWithValues.add(col); } } } int counter = 0; for (int i = 0; i < _fldIndex; ++i) { Object inpt = input[i]; if (inpt == null) { // append 'is NULL' in case the value is null while (counter < colsWithValues.size() && i == colsWithValues.get(counter).getFieldIndex()) { colsWithValues.get(counter).setValue(null); counter++; } } else if (inpt instanceof Identity) { Identity identity = (Identity) inpt; int indx = 0; while (counter < colsWithValues.size() && i == colsWithValues.get(counter).getFieldIndex()) { if (identity.get(indx) != null) { colsWithValues.get(counter).setValue(identity.get(indx)); } indx++; counter++; } if (identity.size() != indx) { throw new PersistenceException("Size of identity field mismatch!"); } } else { while (counter < colsWithValues.size() && i == colsWithValues.get(counter).getFieldIndex()) { colsWithValues.get(counter).setValue(inpt); counter++; } } } return colsWithValues; } //----------------------------------------------------------------------------------- /** * Method setting given extendedTable. * * @param extendedTable ExtendedTable to be set. */ public void setExtendedTable(final TableInfo extendedTable) { _extendedTable = extendedTable; } /** * Method returning extendedTable currently set. * * @return ExtendedTable currently set. */ public TableInfo getExtendedTable() { return _extendedTable; } /** * Method returning classDescriptor currently set. * * @return ClassDescriptor currently set. */ public ClassDescriptor getClassDescriptor() { return _clsDesc; } /** * Method to add a single column to the columns list. * * @param col Column to be added. */ public void addCol(final ColInfo col) { _columns.add(col); } /** * Method returning columns currently set. * * @return List of columns currently set. */ public List<ColInfo> getColumns() { return _columns; } /** * Method to add a new foreign key to this TableInfo. * * @param lnk TableLink representing foreign key to be added. */ public void addFkColumn(final TableLink lnk) { _fks.add(lnk); } /** * Method returning list of foreign keys. * * @return List of foreign keys. */ public List<TableLink> getFkColumns() { return _fks; } /** * Method to add a single primary key column. * * @param col Column to be added to the primary keys list. */ public void addPkColumn(final ColInfo col) { _pkColumns.add(col); } /** * Method returning primary key columns currently set. * * @return List of primary key columns. */ public List<ColInfo> getPkColumns() { return _pkColumns; } /** * Method returning name of this table. * * @return Name of the table currently set. */ public String getTableName() { return _tableName; } /** * Method returning list of tables extending this one. * * @return List of extending tables. */ public List<TableInfo> getExtendingTables() { return _extendingTables; } //----------------------------------------------------------------------------------- }