001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/sql/transaction/insert/InsertRow.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 package org.deegree.io.datastore.sql.transaction.insert; 037 038 import java.util.ArrayList; 039 import java.util.Collection; 040 import java.util.HashMap; 041 import java.util.HashSet; 042 import java.util.Iterator; 043 import java.util.LinkedHashMap; 044 import java.util.List; 045 import java.util.Map; 046 import java.util.Set; 047 048 import org.deegree.framework.log.ILogger; 049 import org.deegree.framework.log.LoggerFactory; 050 import org.deegree.i18n.Messages; 051 import org.deegree.io.datastore.TransactionException; 052 import org.deegree.ogcwebservices.wfs.operation.transaction.Insert; 053 054 /** 055 * Represents a table row (columns + values) which has to be inserted as part of an {@link Insert} operation. 056 * 057 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a> 058 * @author last edited by: $Author: mschneider $ 059 * 060 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 061 */ 062 public class InsertRow { 063 064 private static final ILogger LOG = LoggerFactory.getLogger( InsertRow.class ); 065 066 /** Database table */ 067 protected String table; 068 069 /** Maps column names to their corresponding {@link InsertField} instance. */ 070 protected Map<String, InsertField> columnMap = new LinkedHashMap<String, InsertField>(); 071 072 /** 073 * Creates a new <code>InsertRow</code> instance for the given table. 074 * 075 * @param table 076 */ 077 public InsertRow( String table ) { 078 this.table = table; 079 } 080 081 /** 082 * Returns the name of table. 083 * 084 * @return the name of table 085 */ 086 public String getTable() { 087 return this.table; 088 } 089 090 /** 091 * Sets the value to be inserted in the given table column. 092 * <p> 093 * In complex + erroneous mappings (or feature instances), it may happen that different property values (mapped to 094 * the same column) imply different values. This is checked for and in case an {@link TransactionException} is 095 * thrown. 096 * 097 * @param column 098 * @param value 099 * @param sqlType 100 * @param isPK 101 * @return column + value to be set 102 * @throws TransactionException 103 * if the value for the column clashes 104 */ 105 public InsertField setColumn( String column, Object value, int sqlType, boolean isPK ) 106 throws TransactionException { 107 108 InsertField field = new InsertField( this, column, sqlType, value, isPK ); 109 InsertField presentField = columnMap.get( column ); 110 if ( presentField != null && ( !presentField.getValue().toString().equals( value.toString() ) ) ) { 111 Object[] params = new Object[] { column, this.table, presentField.getValue().toString(), value.toString() }; 112 String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params ); 113 throw new TransactionException( msg ); 114 } 115 if ( presentField == null ) { 116 this.columnMap.put( column, field ); 117 } 118 // TODO type check 119 return field; 120 } 121 122 /** 123 * Sets the value to be inserted in the given geometry column. 124 * <p> 125 * In complex + erroneous mappings (or feature instances), it may happen that different property values (mapped to 126 * the same column) imply different values. This is checked for and in case an {@link TransactionException} is 127 * thrown. 128 * 129 * @param column 130 * @param value 131 * @param sqlType 132 * @param isPK 133 * @param internalSrsCode 134 * @return column + value to be set 135 * @throws TransactionException 136 * if the value for the column clashes 137 */ 138 public InsertGeometryField setGeometryColumn( String column, Object value, int sqlType, boolean isPK, 139 int internalSrsCode ) 140 throws TransactionException { 141 142 InsertGeometryField field = new InsertGeometryField( this, column, sqlType, value, isPK, internalSrsCode ); 143 InsertField presentField = columnMap.get( column ); 144 if ( presentField != null && ( !presentField.getValue().toString().equals( value.toString() ) ) ) { 145 Object[] params = new Object[] { column, this.table, presentField.getValue().toString(), value.toString() }; 146 String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params ); 147 throw new TransactionException( msg ); 148 } 149 if ( presentField == null ) { 150 this.columnMap.put( column, field ); 151 } 152 // TODO type check 153 return field; 154 } 155 156 /** 157 * Sets the value to be inserted in the given table column - the column references the given {@link InsertField} and 158 * thus must have the same value as the referenced column. 159 * <p> 160 * In complex + erroneous mappings (or feature instances), it may happen that different property values (mapped to 161 * the same column) imply different values. This is checked for and in case an {@link TransactionException} is 162 * thrown. 163 * 164 * @param column 165 * @param referencedField 166 * @throws TransactionException 167 * if the value for the column clashes 168 */ 169 public void linkColumn( String column, InsertField referencedField ) 170 throws TransactionException { 171 172 if ( referencedField.getValue() == null ) { 173 LOG.logError( "linkColumn (): referencedField is null" ); 174 throw new TransactionException( "linkColumn (): referenced field is null!" ); 175 } 176 177 InsertField presentField = this.columnMap.get( column ); 178 if ( presentField != null 179 && ( !presentField.getValue().toString().equals( referencedField.getValue().toString() ) ) ) { 180 Object[] params = new Object[] { column, this.table, presentField.getValue().toString(), 181 referencedField.getValue().toString() }; 182 String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params ); 183 throw new TransactionException( msg ); 184 } 185 186 InsertField field = presentField; 187 if ( presentField != null ) { 188 presentField.relinkField( referencedField ); 189 } else { 190 field = new InsertField( this, column, referencedField ); 191 this.columnMap.put( column, field ); 192 } 193 referencedField.addReferencingField( field ); 194 } 195 196 /** 197 * Returns all {@link InsertField}s. 198 * 199 * @return all InsertFields 200 */ 201 public Collection<InsertField> getColumns() { 202 return this.columnMap.values(); 203 } 204 205 /** 206 * Returns the {@link InsertField} for the given column name. 207 * 208 * @param column 209 * requested column name 210 * @return the InsertField for the given column name, or null if it does not exist 211 */ 212 public InsertField getColumn( String column ) { 213 return this.columnMap.get( column ); 214 } 215 216 /** 217 * Returns the {@link InsertField} for the primary key column. 218 * 219 * @return the InsertField for the primary key column 220 */ 221 public InsertField getPKColumn() { 222 InsertField pkField = null; 223 for ( InsertField field : this.columnMap.values() ) { 224 if ( field.isPK() ) { 225 pkField = field; 226 break; 227 } 228 } 229 return pkField; 230 } 231 232 /** 233 * Returns all {@link InsertField} that reference a column in this <code>InsertRow</code>. The fields may well be 234 * from other tables. 235 * 236 * @return all InsertFields that reference a column in this InsertRow 237 */ 238 public List<InsertField> getReferencingFields() { 239 List<InsertField> referencingFields = new ArrayList<InsertField>(); 240 Iterator<InsertField> iter = this.columnMap.values().iterator(); 241 while ( iter.hasNext() ) { 242 InsertField field = iter.next(); 243 referencingFields.addAll( field.getReferencingFields() ); 244 } 245 return referencingFields; 246 } 247 248 /** 249 * Returns all {@link InsertRow}s that reference a column in this <code>InsertRow</code>. The rows may well be from 250 * other tables. 251 * 252 * @return all InsertRows that reference a column in this InsertRow 253 */ 254 Collection<InsertRow> getReferencingRows() { 255 Set<InsertRow> referencingRows = new HashSet<InsertRow>(); 256 Iterator<InsertField> iter = this.columnMap.values().iterator(); 257 while ( iter.hasNext() ) { 258 InsertField field = iter.next(); 259 referencingRows.addAll( field.getReferencingRows() ); 260 } 261 return referencingRows; 262 } 263 264 /** 265 * Returns all {@link InsertField}s that are referenced by a field from this <code>InsertRow</code>. The fields may 266 * well be from other tables. 267 * 268 * @return all InsertField that are referenced by a field from this InsertRow 269 */ 270 List<InsertField> getReferencedFields() { 271 List<InsertField> referencedFields = new ArrayList<InsertField>(); 272 Iterator<InsertField> iter = this.columnMap.values().iterator(); 273 while ( iter.hasNext() ) { 274 InsertField field = iter.next(); 275 InsertField referencedField = field.getReferencedField(); 276 if ( referencedField != null ) { 277 referencedFields.add( referencedField ); 278 } 279 } 280 return referencedFields; 281 } 282 283 /** 284 * Returns all {@link InsertRow}s that are referenced by a field from this <code>InsertRow</code>. The rows may well 285 * be from other tables. 286 * 287 * @return all InsertRows that are referenced by a field from this InsertRow 288 */ 289 private Collection<InsertRow> getReferencedRows() { 290 Set<InsertRow> referencedRows = new HashSet<InsertRow>(); 291 Iterator<InsertField> iter = this.columnMap.values().iterator(); 292 while ( iter.hasNext() ) { 293 InsertField field = iter.next(); 294 InsertRow referencedRow = field.getReferencedRow(); 295 if ( referencedRow != null ) { 296 referencedRows.add( referencedRow ); 297 } 298 } 299 return referencedRows; 300 } 301 302 /** 303 * Sorts the given <code>InsertRow</code>s topologically (respecting the foreign key constraints), so they can be 304 * inserted in the resulting order without causing foreign key violations. 305 * <p> 306 * Number of precedessors (pre): number of fields that *are referenced by* this row Number of successors (post) : 307 * number of fields that *reference* this row 308 * 309 * @param inserts 310 * insert rows to sort 311 * @return the nodes of the graph in topological order if no cycle is present, else in arbitrary order 312 */ 313 public static List<InsertRow> getInsertOrder( List<InsertRow> inserts ) { 314 315 List<InsertRow> result = new ArrayList<InsertRow>(); 316 317 // key: inserts, value: number of fields that are referenced by this row 318 Map<InsertRow, Integer> preMap = new HashMap<InsertRow, Integer>(); 319 320 // key: inserts with no foreign key constraints 321 List<InsertRow> noPre = new ArrayList<InsertRow>(); 322 323 // build map 324 Iterator<InsertRow> insertIter = inserts.iterator(); 325 while ( insertIter.hasNext() ) { 326 InsertRow insertRow = insertIter.next(); 327 int pre = insertRow.getReferencedFields().size(); 328 LOG.logDebug( "Adding row to preMap: " + insertRow ); 329 preMap.put( insertRow, pre ); 330 if ( pre == 0 ) { 331 noPre.add( insertRow ); 332 } 333 } 334 335 while ( !noPre.isEmpty() ) { 336 // select an insert row that has no open fk constraints 337 InsertRow insertRow = noPre.get( 0 ); 338 noPre.remove( 0 ); 339 result.add( insertRow ); 340 341 // decrease the number of pending fk constraints for all insert rows that 342 // reference the currently processed insert row 343 Collection<InsertField> postList = insertRow.getReferencingFields(); 344 Iterator<InsertField> iter = postList.iterator(); 345 while ( iter.hasNext() ) { 346 InsertField postField = iter.next(); 347 if ( preMap.get( postField.getRow() ) == null ) { 348 LOG.logDebug( "No pre info for: " + postField.getRow() ); 349 } 350 int pre = preMap.get( postField.getRow() ); 351 preMap.put( postField.getRow(), --pre ); 352 if ( pre == 0 ) { 353 noPre.add( postField.getRow() ); 354 } 355 } 356 } 357 358 if ( result.size() != inserts.size() ) { 359 Collection<InsertRow> cycle = InsertRow.findCycle( inserts ); 360 Iterator<InsertRow> cycleIter = cycle.iterator(); 361 StringBuffer sb = new StringBuffer(); 362 while ( cycleIter.hasNext() ) { 363 sb.append( cycleIter.next() ); 364 if ( cycle.iterator().hasNext() ) { 365 sb.append( " -> " ); 366 } 367 } 368 String msg = Messages.getMessage( "DATASTORE_FK_CYCLE", sb.toString() ); 369 LOG.logWarning( msg ); 370 result.clear(); 371 result.addAll( inserts ); 372 } 373 return result; 374 } 375 376 /** 377 * Checks if the given <code>InsertRow</code>s contain cyclic fk constraints. 378 * 379 * @param rows 380 * @return steps of the cycle, or null if there is none 381 */ 382 private static Collection<InsertRow> findCycle( Collection<InsertRow> rows ) { 383 384 Iterator<InsertRow> rowsIter = rows.iterator(); 385 while ( rowsIter.hasNext() ) { 386 InsertRow begin = rowsIter.next(); 387 Iterator<InsertRow> refIter = begin.getReferencedRows().iterator(); 388 while ( refIter.hasNext() ) { 389 InsertRow referencedRow = refIter.next(); 390 List<InsertRow> cycle = findCycleRecursion( referencedRow, begin, new ArrayList<InsertRow>() ); 391 if ( cycle != null ) { 392 return cycle; 393 } 394 } 395 } 396 return null; 397 } 398 399 private static List<InsertRow> findCycleRecursion( InsertRow next, InsertRow begin, List<InsertRow> steps ) { 400 401 if ( steps.contains( next ) ) { 402 steps.add( next ); 403 return steps; 404 } 405 steps.add( next ); 406 407 Iterator<InsertRow> refIter = next.getReferencedRows().iterator(); 408 while ( refIter.hasNext() ) { 409 InsertRow referencedRow = refIter.next(); 410 List<InsertRow> cycle = findCycleRecursion( referencedRow, begin, steps ); 411 if ( cycle != null ) { 412 return cycle; 413 } 414 } 415 steps.remove( steps.size() - 1 ); 416 return null; 417 } 418 419 /** 420 * Returns a string representation of the object. 421 * 422 * @return a string representation of the object 423 */ 424 @Override 425 public String toString() { 426 StringBuffer sb = new StringBuffer( "InsertRow, table: '" ); 427 sb.append( this.table ); 428 sb.append( "'" ); 429 Iterator<String> columnIter = this.columnMap.keySet().iterator(); 430 while ( columnIter.hasNext() ) { 431 sb.append( ", " ); 432 String column = columnIter.next(); 433 InsertField field = this.columnMap.get( column ); 434 sb.append( field ); 435 } 436 return sb.toString(); 437 } 438 }