//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/sql/transaction/Attic/InsertRow.java,v 1.18 2006/09/26 16:45:44 mschneider Exp $
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstraße 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.io.datastore.sql.transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.deegree.datatypes.Types;
import org.deegree.datatypes.UnknownTypeException;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.i18n.Messages;
import org.deegree.io.datastore.TransactionException;
import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
/**
* Represents a table row (columns + values) which has to be inserted as part of a {@link Insert}
* operation.
*
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
* @author last edited by: $Author: mschneider $
*
* @version $Revision: 1.18 $, $Date: 2006/09/26 16:45:44 $
*/
public class InsertRow {
static final ILogger LOG = LoggerFactory.getLogger( InsertRow.class );
protected String table;
// key: column name, value: InsertField
protected Map<String, InsertField> columnMap = new LinkedHashMap<String, InsertField>();
/**
* Creates a new <code>InsertRow</code> instance for the given table.
*
* @param table
*/
public InsertRow( String table ) {
this.table = table;
}
/**
* Returns the name of table.
*
* @return the name of table
*/
public String getTable() {
return this.table;
}
/**
* Sets the value to be inserted in the given table column.
* <p>
* In complex + erroneous mappings (or feature instances), it may happen that different
* property values (mapped to the same column) imply different values. This is checked for
* and in case an {@link TransactionException} is thrown.
*
* @param column
* @param value
* @param sqlType
* @param isPK
* @return column + value to be set
* @throws TransactionException
* if the value for the column clashes
*/
public InsertField setColumn( String column, Object value, int sqlType, boolean isPK )
throws TransactionException {
InsertField field = new InsertField( this, column, sqlType, value, isPK );
InsertField presentField = columnMap.get( column );
if ( presentField != null
&& ( !presentField.getValue().toString().equals( value.toString() ) ) ) {
Object[] params = new Object[] { column, this.table,
presentField.getValue().toString(), value.toString() };
String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params );
throw new TransactionException( msg );
}
if ( presentField == null ) {
this.columnMap.put( column, field );
}
// TODO type check
return field;
}
/**
* Sets the value to be inserted in the given table column - the column references the given
* {@link InsertField} and thus must have the same value as the referenced column.
* <p>
* In complex + erroneous mappings (or feature instances), it may happen that different
* property values (mapped to the same column) imply different values. This is checked for
* and in case an {@link TransactionException} is thrown.
*
* @param column
* @param referencedField
* @throws TransactionException
* if the value for the column clashes
*/
public void linkColumn( String column, InsertField referencedField )
throws TransactionException {
if ( referencedField.getValue() == null ) {
LOG.logError( "linkColumn (): referencedField is null" );
throw new TransactionException( "linkColumn (): referenced field is null!" );
}
InsertField presentField = this.columnMap.get( column );
if ( presentField != null
&& ( !presentField.getValue().toString().equals( referencedField.getValue().toString() ) ) ) {
Object[] params = new Object[] { column, this.table,
presentField.getValue().toString(),
referencedField.getValue().toString() };
String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params );
throw new TransactionException( msg );
}
InsertField field = presentField;
if ( presentField != null ) {
presentField.relinkField( referencedField );
} else {
field = new InsertField( this, column, referencedField );
this.columnMap.put( column, field );
}
referencedField.addReferencingField( field );
}
/**
* Returns all {@link InsertField}s.
*
* @return all InsertFields
*/
public Collection<InsertField> getColumns() {
return this.columnMap.values();
}
/**
* Returns the {@link InsertField} for the given column name.
*
* @param column
* requested column name
* @return the InsertField for the given column name, or null if it does not exist
*/
public InsertField getColumn( String column ) {
return this.columnMap.get( column );
}
/**
* Returns the {@link InsertField} for the primary key column.
*
* @return the InsertField for the primary key column
*/
public InsertField getPKColumn() {
InsertField pkField = null;
for ( InsertField field : this.columnMap.values() ) {
if ( field.isPK() ) {
pkField = field;
break;
}
}
return pkField;
}
/**
* Returns all {@link InsertField} that reference a column in this <code>InsertRow</code>.
* The fields may well be from other tables.
*
* @return all InsertFields that reference a column in this InsertRow
*/
public List<InsertField> getReferencingFields() {
List<InsertField> referencingFields = new ArrayList<InsertField>();
Iterator<InsertField> iter = this.columnMap.values().iterator();
while ( iter.hasNext() ) {
InsertField field = iter.next();
referencingFields.addAll( field.getReferencingFields() );
}
return referencingFields;
}
/**
* Returns all {@link InsertRows}s that reference a column in this <code>InsertRow</code>.
* The rows may well be from other tables.
*
* @return all InsertRows that reference a column in this InsertRow
*/
Collection<InsertRow> getReferencingRows() {
Set<InsertRow> referencingRows = new HashSet<InsertRow>();
Iterator<InsertField> iter = this.columnMap.values().iterator();
while ( iter.hasNext() ) {
InsertField field = iter.next();
referencingRows.addAll( field.getReferencingRows() );
}
return referencingRows;
}
/**
* Returns all {@link InsertField}s that are referenced by a field from this
* <code>InsertRow</code>. The fields may well be from other tables.
*
* @return all InsertField that are referenced by a field from this InsertRow
*/
List<InsertField> getReferencedFields() {
List<InsertField> referencedFields = new ArrayList<InsertField>();
Iterator<InsertField> iter = this.columnMap.values().iterator();
while ( iter.hasNext() ) {
InsertField field = iter.next();
InsertField referencedField = field.getReferencedField();
if ( referencedField != null ) {
referencedFields.add( referencedField );
}
}
return referencedFields;
}
/**
* Returns all {@link InsertRow}s that are referenced by a field from this
* <code>InsertRow</code>. The rows may well be from other tables.
*
* @return all InsertRows that are referenced by a field from this InsertRow
*/
private Collection<InsertRow> getReferencedRows() {
Set<InsertRow> referencedRows = new HashSet<InsertRow>();
Iterator<InsertField> iter = this.columnMap.values().iterator();
while ( iter.hasNext() ) {
InsertField field = iter.next();
InsertRow referencedRow = field.getReferencedRow();
if ( referencedRow != null ) {
referencedRows.add( referencedRow );
}
}
return referencedRows;
}
/**
* Sorts the given <code>InsertRow</code>s topologically (respecting the foreign key
* constraints), so they can be inserted in the resulting order without causing foreign key
* violations.
* <p>
* Number of precedessors (pre): number of fields that *are referenced by* this row
* Number of successors (post) : number of fields that *reference* this row
*
* @param inserts
* insert rows to sort
* @return the given insert rows in topological order
* @throws TransactionException
* if there is no topological order (i.e. there is a cyclic constraint)
*/
public static List<InsertRow> sortInsertRows( List<InsertRow> inserts )
throws TransactionException {
List<InsertRow> result = new ArrayList<InsertRow>();
// key: inserts, value: number of fields that are referenced by this row
Map<InsertRow, Integer> preMap = new HashMap<InsertRow, Integer>();
// key: inserts with no foreign key constraints
List<InsertRow> noPre = new ArrayList<InsertRow>();
// build map
Iterator<InsertRow> insertIter = inserts.iterator();
while ( insertIter.hasNext() ) {
InsertRow insertRow = insertIter.next();
int pre = insertRow.getReferencedFields().size();
LOG.logDebug( "Adding row to preMap: " + insertRow );
preMap.put( insertRow, pre );
if ( pre == 0 ) {
noPre.add( insertRow );
}
}
while ( !noPre.isEmpty() ) {
// select an insert row that has no open fk constraints
InsertRow insertRow = noPre.get( 0 );
noPre.remove( 0 );
result.add( insertRow );
// decrease the number of pending fk constraints for all insert rows that
// reference the currently processed insert row
Collection<InsertField> postList = insertRow.getReferencingFields();
Iterator<InsertField> iter = postList.iterator();
while ( iter.hasNext() ) {
InsertField postField = iter.next();
if ( preMap.get( postField.row ) == null ) {
LOG.logDebug( "No pre info for: " + postField.row );
}
int pre = preMap.get( postField.row );
preMap.put( postField.row, --pre );
if ( pre == 0 ) {
noPre.add( postField.row );
}
}
}
if ( result.size() != inserts.size() ) {
throw new TransactionException( "Cannot process Insert: cycle in foreign "
+ "key constraints." );
}
return result;
}
/**
* Checks if the given <code>InsertRow</code>s contain cyclic fk constraints.
*
* @param rows
* @return steps of the cycle, or null if there is none
*/
public static Collection<InsertRow> findCycle( Collection<InsertRow> rows ) {
Iterator<InsertRow> rowsIter = rows.iterator();
while ( rowsIter.hasNext() ) {
InsertRow begin = rowsIter.next();
Iterator<InsertRow> refIter = begin.getReferencedRows().iterator();
while ( refIter.hasNext() ) {
InsertRow referencedRow = refIter.next();
List<InsertRow> cycle = findCycleRecursion( referencedRow, begin,
new ArrayList<InsertRow>() );
if ( cycle != null ) {
return cycle;
}
}
}
return null;
}
private static List<InsertRow> findCycleRecursion( InsertRow next, InsertRow begin,
List<InsertRow> steps ) {
if ( steps.contains( next ) ) {
steps.add( next );
return steps;
}
steps.add( next );
Iterator<InsertRow> refIter = next.getReferencedRows().iterator();
while ( refIter.hasNext() ) {
InsertRow referencedRow = refIter.next();
List<InsertRow> cycle = findCycleRecursion( referencedRow, begin, steps );
if ( cycle != null ) {
return cycle;
}
}
steps.remove( steps.size() - 1 );
return null;
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer( "InsertRow, table: '" );
sb.append( this.table );
sb.append( "'" );
Iterator<String> columnIter = this.columnMap.keySet().iterator();
while ( columnIter.hasNext() ) {
sb.append( ", " );
String column = columnIter.next();
InsertField field = this.columnMap.get( column );
sb.append( field );
}
return sb.toString();
}
/**
* A field of an {@link InsertRow}.
*
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
* @author last edited by: $Author: mschneider $
*
* @version $Revision: 1.18 $, $Date: 2006/09/26 16:45:44 $
*/
public static class InsertField {
private String columnName;
private Object value;
private int sqlType;
private boolean isPK;
private InsertField referencedField;
private List<InsertField> referencingFields = new ArrayList<InsertField>();
InsertRow row;
InsertField( InsertRow row, String columnName, int sqlType, Object value,
boolean isPK ) {
this.row = row;
this.columnName = columnName;
this.sqlType = sqlType;
this.value = value;
this.isPK = isPK;
}
InsertField( InsertRow row, String columnName, InsertField referencedField ) {
this.row = row;
this.columnName = columnName;
this.referencedField = referencedField;
}
/**
* Returns the name of the table that this <code>InsertField</code> belongs to.
*
* @return the name of the table that this <code>InsertField</code> belongs to
*/
public String getTable() {
return this.row.getTable();
}
/**
* Returns whether this <code>InsertField</code> is a primary key of the table.
*
* @return true, if this InsertField is a primary key of the table, false otherwise
*/
public boolean isPK() {
return this.isPK;
}
/**
* Returns the name of the column that this <code>InsertField</code> represents.
*
* @return the name of the column that this <code>InsertField</code> represents
*/
public String getColumnName() {
return this.columnName;
}
/**
* Returns the sql type code for the column.
*
* @return the sql type code for the column
*/
public int getSQLType() {
if ( this.referencedField != null ) {
return this.referencedField.getSQLType();
}
return this.sqlType;
}
/**
* Returns the value to be inserted.
*
* @return the value to be inserted
*/
public Object getValue() {
if ( this.referencedField != null ) {
return this.referencedField.getValue();
}
return this.value;
}
/**
* Returns the <code>InsertField</code> that this <code>InsertField</code> references.
*
* @return the referenced InsertField or null, if no field is referenced
*/
public InsertField getReferencedField() {
return this.referencedField;
}
InsertRow getReferencedRow() {
if ( this.referencedField == null ) {
return null;
}
return this.referencedField.row;
}
/**
* Sets this <code>InsertField</code> to be a reference to the given
* <code>InsertField</code>.
*
* @param field
*/
public void relinkField( InsertField field ) {
if ( referencedField != null ) {
this.referencedField.removeReferencingField( this );
}
this.referencedField = field;
field.addReferencingField( this );
}
void addReferencingField( InsertField referencingField ) {
if ( referencingField == null ) {
LOG.logError( "addReferencingField() called with null value" );
} else {
this.referencingFields.add( referencingField );
}
}
/**
* Removes the given <code>InsertField</code> from the list of references to
* this <code>InsertField</code>.
*
* @param referencingField
*/
public void removeReferencingField( InsertField referencingField ) {
this.referencingFields.remove( referencingField );
}
Collection<InsertField> getReferencingFields() {
return this.referencingFields;
}
Collection<InsertRow> getReferencingRows() {
List<InsertRow> referencingRows = new ArrayList<InsertRow>();
Iterator<InsertField> iter = this.referencingFields.iterator();
while ( iter.hasNext() ) {
referencingRows.add( iter.next().row );
}
return referencingRows;
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
String stringValue = getValue().toString();
if ( stringValue.length() > 20 ) {
stringValue = stringValue.substring( 0, 19 );
stringValue += "...";
}
int type = getSQLType();
String typeString = "" + type;
try {
typeString = Types.getTypeNameForSQLTypeCode( type );
} catch ( UnknownTypeException e ) {
LOG.logError( e.getMessage(), e );
}
sb.append( getColumnName() );
sb.append( "(" );
sb.append( typeString );
sb.append( ")='" );
sb.append( stringValue );
sb.append( '\'' );
InsertField referencedField = getReferencedField();
if ( referencedField != null ) {
sb.append( " [fk to '" );
sb.append( referencedField.getTable() );
sb.append( '.' );
sb.append( referencedField.getColumnName() );
sb.append( "']" );
}
return sb.toString();
}
}
}
/***************************************************************************************************
* Changes to this class. What the people have been up to:
* $Log: InsertRow.java,v $
* Revision 1.18 2006/09/26 16:45:44 mschneider
* Javadoc corrections + fixed warnings.
*
* Revision 1.17 2006/09/20 11:35:41 mschneider
* Merged datastore related messages with org.deegree.18n.
*
* Revision 1.16 2006/09/05 14:43:24 mschneider
* Adapted due to merging of messages.
*
* Revision 1.15 2006/05/21 19:07:16 poth
* several methods set to public; required by SDE datastore
*
* Revision 1.14 2006/04/26 13:34:09 poth
* *** empty log message ***
*
* Revision 1.13 2006/04/19 18:27:03 mschneider
* Fixed wrong import.
*
* Revision 1.12 2006/04/07 17:13:36 mschneider
* Added method getPKColumn().
*
* Revision 1.11 2006/04/06 20:25:27 poth
* *** empty log message ***
*
* Revision 1.10 2006/04/04 17:51:35 mschneider
* Made removeReferencingField() public.
*
* Revision 1.9 2006/03/09 12:56:15 mschneider
* Improved javadoc. Fixed cycle check.
*
* Revision 1.8 2006/03/03 13:36:18 mschneider
* Removed unnecessary null checks.
*
* Revision 1.7 2006/03/02 18:03:51 poth
* *** empty log message ***
*
* Revision 1.6 2006/02/24 14:35:11 mschneider
* Enabled checking for ambigous field values.
*
* Revision 1.5 2006/02/23 15:30:04 mschneider
* Fixes.
*
* Revision 1.4 2006/02/23 13:06:02 mschneider
* Added findCycle().
*
* Revision 1.3 2006/02/22 02:33:34 mschneider
* Added merging of "equal" InsertRows.
*
* Revision 1.2 2006/02/20 16:33:30 mschneider
* More work on Insert functionality.
*
* Revision 1.1 2006/02/17 14:41:08 mschneider
* Initial version.
*
**************************************************************************************************/