Changeset 1591 for trunk/grails-app/services/dbnp
- Timestamp:
- Mar 7, 2011, 12:01:52 PM (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/grails-app/services/dbnp/importer/ImporterService.groovy
r1588 r1591 15 15 */ 16 16 package dbnp.importer 17 17 18 import org.dbnp.gdt.* 18 19 import org.apache.poi.ss.usermodel.* … … 186 187 } 187 188 189 190 /** 191 * Retrieves records with sample, subject, samplingevent etc. from a study 192 * @param s Study to retrieve records from 193 * @return A list with hashmaps [ 'objects': [ 'Sample': .., 'Subject': .., 'SamplingEvent': .., 'Event': '.. ], 'templates': [], 'templateCombination': .. ] 194 */ 195 protected def getRecords( Study s ) { 196 def records = []; 197 198 s.samples?.each { 199 def record = [ 'objects': retrieveEntitiesBySample( it ) ]; 200 201 def templates = [:] 202 def templateCombination = []; 203 record.objects.each { entity -> 204 templates[ entity.key ] = entity.value?.template 205 if( entity.value?.template ) 206 templateCombination << entity.key + ": " + entity.value?.template?.name; 207 } 208 209 record.templates = templates; 210 record.templateCombination = templateCombination.join( ', ' ) 211 212 records << record 213 } 214 215 return records; 216 } 217 218 /** 219 * Returns a subject, event and samplingEvent that belong to this sample 220 * @param s Sample to find the information for 221 * @return 222 */ 223 protected retrieveEntitiesBySample( Sample s ) { 224 return [ 225 'Sample': s, 226 'Subject': s?.parentSubject, 227 'SamplingEvent': s?.parentEvent, 228 'Event': s?.parentEventGroup?.events?.getAt(0) 229 ] 230 } 231 232 /** 233 * Imports data from a workbook into a list of ImportRecords. If some entities are already in the database, 234 * these records are updated. 235 * 236 * This method is capable of importing Subject, Samples, SamplingEvents and Events 237 * 238 * @param templates Map of templates, identified by their entity as a key. For example: [ Subject: Template x, Sample: Template y ] 239 * @param wb Excel workbook to import 240 * @param sheetindex Number of the sheet to import data from 241 * @param rowindex Row to start importing from. 242 * @param mcmap Hashmap of mappingcolumns, with the first entry in the hashmap containing information about the first column, etc. 243 * @param parent Study to import all data into. Is used for determining which sample/event/subject/assay to update 244 * @param createAllEntities If set to true, the system will also create objects for entities that have no data imported, but do have 245 * a template assigned 246 * @return List List with two entries: 247 * 0 List with ImportRecords, one for each row in the excelsheet 248 * 1 List with ImportCell objects, mentioning the cells that could not be correctly imported 249 * (because the value in the excelsheet can't be entered into the template field) 250 */ 251 def importOrUpdateDataBySampleIdentifier( def templates, Workbook wb, int sheetindex, int rowindex, def mcmap, Study parent = null, boolean createAllEntities = true ) { 252 if( !mcmap ) 253 return; 254 255 // Check whether the rows should be imported in one or more entities 256 def entities 257 if( createAllEntities ) { 258 entities = templates.entrySet().value.findAll { it }.entity; 259 } else { 260 entities = mcmap.findAll{ !it.dontimport }.entityclass.unique(); 261 } 262 263 def sheet = wb.getSheetAt(sheetindex) 264 def table = [] 265 def failedcells = [] // list of cells that have failed to import 266 267 // First check for each record whether an entity in the database should be updated, 268 // or a new entity should be added. This is done before any new object is created, since 269 // searching after new objects have been created (but not yet saved) will result in 270 // org.hibernate.AssertionFailure: collection [...] was not processed by flush() 271 // errors 272 def existingEntities = [:] 273 for( int i = rowindex; i <= sheet.getLastRowNum(); i++ ) { 274 existingEntities[i] = findExistingEntities( entities, sheet.getRow(i), mcmap, parent ); 275 } 276 277 // walk through all rows and fill the table with records 278 for( int i = rowindex; i <= sheet.getLastRowNum(); i++ ) { 279 // Create an entity record based on a row read from Excel and store the cells which failed to be mapped 280 def (record, failed) = importOrUpdateRecord( templates, entities, sheet.getRow(i), mcmap, parent, table, existingEntities[i] ); 281 282 // Setup the relationships between the imported entities 283 relateEntities( record ); 284 285 // Add record with entities and its values to the table 286 table.add(record) 287 288 // If failed cells have been found, add them to the failed cells list 289 if (failed?.importcells?.size() > 0) failedcells.add(failed) 290 } 291 292 return [ "table": table, "failedCells": failedcells ] 293 } 294 295 /** 296 * Checks whether entities in the given row already exist in the database 297 * they are updated. 298 * 299 * @param entities Entities that have to be imported for this row 300 * @param excelRow Excel row to import into this record 301 * @param mcmap Hashmap of mappingcolumns, with the first entry in the hashmap containing information about the first column, etc. 302 * @return Map Map with entities that have been found for this row. The key for the entities is the entity name (e.g.: [Sample: null, Subject: <subject object>] 303 */ 304 def findExistingEntities(def entities, Row excelRow, mcmap, parent ) { 305 DataFormatter df = new DataFormatter(); 306 307 // Find entities based on sample identifier 308 def sample = findEntityByRow( dbnp.studycapturing.Sample, excelRow, mcmap, parent, [], df ); 309 return retrieveEntitiesBySample( sample ); 310 } 311 312 /** 313 * Imports a records from the excelsheet into the database. If the entities are already in the database 314 * they are updated. 315 * 316 * This method is capable of importing Subject, Samples, SamplingEvents and Events 317 * 318 * @param templates Map of templates, identified by their entity as a key. For example: [ Sample: Template y ] 319 * @param entities Entities that have to be imported for this row 320 * @param excelRow Excel row to import into this record 321 * @param mcmap Hashmap of mappingcolumns, with the first entry in the hashmap containing information about the first column, etc. 322 * @param parent Study to import all data into. Is used for determining which sample/event/subject/assay to update 323 * @param importedRows Rows that have been imported before this row. These rows might contain the same entities as are 324 * imported in this row. These entities should be used again, to avoid importing duplicates. 325 * @return List List with two entries: 326 * 0 List with ImportRecords, one for each row in the excelsheet 327 * 1 List with ImportCell objects, mentioning the cells that could not be correctly imported 328 * (because the value in the excelsheet can't be entered into the template field) 329 */ 330 def importOrUpdateRecord(def templates, def entities, Row excelRow, mcmap, Study parent = null, List importedRows, Map existingEntities ) { 331 DataFormatter df = new DataFormatter(); 332 def record = [] // list of entities and the read values 333 def failed = new ImportRecord() // map with entity identifier and failed mappingcolumn 334 335 // Check whether this record mentions a sample that has been imported before. In that case, 336 // we update that record, in order to prevent importing the same sample multiple times 337 def importedEntities = []; 338 if( importedRows ) 339 importedEntities = importedRows.flatten().findAll { it.class == dbnp.studycapturing.Sample }.unique(); 340 341 def importedSample = findEntityInImportedEntities( dbnp.studycapturing.Sample, excelRow, mcmap, importedEntities, df ) 342 def imported = retrieveEntitiesBySample( importedSample ); 343 344 for( entity in entities ) { 345 // Check whether this entity should be added or updated 346 // The entity is updated is an entity with the same 'identifier' (field 347 // specified to be the identifying field) is found in the database 348 def entityName = entity.name[ entity.name.lastIndexOf( '.' ) + 1..-1]; 349 def template = templates[ entityName ]; 350 351 // If no template is specified for this entity, continue with the next 352 if( !template ) 353 continue; 354 355 // Check whether the object exists in the list of already imported entities 356 def entityObject = imported[ entityName ] 357 358 // If it doesn't, search for the entity in the database 359 if( !entityObject && existingEntities ) 360 entityObject = existingEntities[ entityName ]; 361 362 // Otherwise, create a new object 363 if( !entityObject ) 364 entityObject = entity.newInstance(); 365 366 // Update the template 367 entityObject.template = template; 368 369 // Go through the Excel row cell by cell 370 for (Cell cell: excelRow) { 371 // get the MappingColumn information of the current cell 372 def mc = mcmap[cell.getColumnIndex()] 373 def value 374 375 // Check if column must be imported 376 if (mc != null && !mc.dontimport && mc.entityclass == entity) { 377 try { 378 value = formatValue(df.formatCellValue(cell), mc.templatefieldtype) 379 } catch (NumberFormatException nfe) { 380 value = "" 381 } 382 383 try { 384 entityObject.setFieldValue(mc.property, value) 385 } catch (Exception iae) { 386 log.error ".import wizard error could not set property `" + mc.property + "` to value `" + value + "`" 387 388 // store the mapping column and value which failed 389 def identifier = entityName.toLowerCase() + "_" + entityObject.getIdentifier() + "_" + mc.property 390 391 def mcInstance = new MappingColumn() 392 mcInstance.properties = mc.properties 393 failed.addToImportcells(new ImportCell(mappingcolumn: mcInstance, value: value, entityidentifier: identifier)) 394 } 395 } // end if 396 } // end for 397 398 // If a Study is entered, use it as a 'parent' for other entities 399 if( entity == Study ) 400 parent = entityObject; 401 402 record << entityObject; 403 } 404 405 // a failed column means that using the entity.setFieldValue() threw an exception 406 return [record, failed] 407 } 408 409 /** 410 * Looks into the database to find an object of the given entity that should be updated, given the excel row. 411 * This is done by looking at the 'preferredIdentifier' field of the object. If it exists in the row, and the 412 * value is already in the database for that field, an existing object is returned. Otherwise, null is returned 413 * 414 * @param entity Entity to search 415 * @param excelRow Excelrow to search for 416 * @param mcmap Map with MappingColumns 417 * @param parent Parent study for the entity (if applicable). The returned entity will also have this parent 418 * @param importedRows List of entities that have been imported before. The function will first look through this list to find 419 * a matching entity. 420 * @return An entity that has the same identifier as entered in the excelRow. The entity is first sought in the importedRows. If it 421 * is not found there, the database is queried. If no entity is found at all, null is returned. 422 */ 423 def findEntityByRow( Class entity, Row excelRow, def mcmap, Study parent = null, List importedEntities = [], DataFormatter df = null ) { 424 if( df == null ) 425 df = new DataFormatter(); 426 427 def identifierField = givePreferredIdentifier( entity ); 428 429 if( identifierField ) { 430 // Check whether the identifierField is chosen in the column matching 431 def identifierColumn = mcmap.find { it.entityclass == entity && it.property == identifierField.name }; 432 433 // If it is, find the identifier and look it up in the database 434 if( identifierColumn ) { 435 def identifierCell = excelRow.getCell( identifierColumn.index ); 436 def identifier; 437 try { 438 identifier = formatValue(df.formatCellValue(identifierCell), identifierColumn.templatefieldtype) 439 } catch (NumberFormatException nfe) { 440 identifier = null 441 } 442 443 // Search for an existing object with the same identifier. 444 if( identifier ) { 445 // First search the already imported rows 446 if( importedEntities ) { 447 def imported = importedEntities.find { it.getFieldValue( identifierField.name ) == identifier }; 448 if( imported ) 449 return imported; 450 } 451 452 def c = entity.createCriteria(); 453 454 // If the entity has a field 'parent', the search should be limited to 455 // objects with the same parent. The method entity.hasProperty( "parent" ) doesn't 456 // work, since the java.lang.Class entity doesn't know of the parent property. 457 if( entity.belongsTo?.containsKey( "parent" ) ) { 458 // If the entity requires a parent, but none is given, no 459 // results are given from the database. This prevents the user 460 // of changing data in another study 461 if( parent && parent.id ) { 462 println "Searching (with parent ) for " + entity.name + " with " + identifierField.name + " = " + identifier 463 return c.get { 464 eq( identifierField.name, identifier ) 465 eq( "parent", parent ) 466 } 467 } 468 } else { 469 println "Searching (without parent ) for " + entity.name + " with " + identifierField.name + " = " + identifier 470 return c.get { 471 eq( identifierField.name, identifier ) 472 } 473 } 474 } 475 } 476 } 477 478 // No object is found 479 return null; 480 } 481 482 /** 483 * Looks into the list of already imported entities to find an object of the given entity that should be 484 * updated, given the excel row. This is done by looking at the 'preferredIdentifier' field of the object. 485 * If it exists in the row, and the list of imported entities contains an object with the same 486 * identifier, the existing object is returned. Otherwise, null is returned 487 * 488 * @param entity Entity to search 489 * @param excelRow Excelrow to search for 490 * @param mcmap Map with MappingColumns 491 * @param importedRows List of entities that have been imported before. The function will first look through this list to find 492 * a matching entity. 493 * @return An entity that has the same identifier as entered in the excelRow. The entity is first sought in the importedRows. If it 494 * is not found there, the database is queried. If no entity is found at all, null is returned. 495 */ 496 def findEntityInImportedEntities( Class entity, Row excelRow, def mcmap, List importedEntities = [], DataFormatter df = null ) { 497 if( df == null ) 498 df = new DataFormatter(); 499 500 def allFields = entity.giveDomainFields(); 501 def identifierField = allFields.find { it.preferredIdentifier } 502 503 if( identifierField ) { 504 // Check whether the identifierField is chosen in the column matching 505 def identifierColumn = mcmap.find { it.entityclass == entity && it.property == identifierField.name }; 506 507 // If it is, find the identifier and look it up in the database 508 if( identifierColumn ) { 509 def identifierCell = excelRow.getCell( identifierColumn.index ); 510 def identifier; 511 try { 512 identifier = formatValue(df.formatCellValue(identifierCell), identifierColumn.templatefieldtype) 513 } catch (NumberFormatException nfe) { 514 identifier = null 515 } 516 517 // Search for an existing object with the same identifier. 518 if( identifier ) { 519 // First search the already imported rows 520 if( importedEntities ) { 521 def imported = importedEntities.find { 522 def fieldValue = it.getFieldValue( identifierField.name ) 523 524 if( fieldValue instanceof String ) 525 return fieldValue.toLowerCase() == identifier.toLowerCase(); 526 else 527 return fieldValue == identifier 528 529 }; 530 if( imported ) 531 return imported; 532 } 533 } 534 } 535 } 536 537 // No object is found 538 return null; 539 } 540 541 542 /** 543 * Creates relation between multiple entities that have been imported. The entities are 544 * all created from one row in the excel sheet. 545 */ 546 def relateEntities( List entities) { 547 def study = entities.find { it instanceof Study } 548 def subject = entities.find { it instanceof Subject } 549 def sample = entities.find { it instanceof Sample } 550 def event = entities.find { it instanceof Event } 551 def samplingEvent = entities.find { it instanceof SamplingEvent } 552 def assay = entities.find { it instanceof Assay } 553 554 // A study object is found in the entity list 555 if( study ) { 556 if( subject ) { 557 subject.parent = study; 558 study.addToSubjects( subject ); 559 } 560 if( sample ) { 561 sample.parent = study 562 study.addToSamples( sample ); 563 } 564 if( event ) { 565 event.parent = study 566 study.addToEvents( event ); 567 } 568 if( samplingEvent ) { 569 samplingEvent.parent = study 570 study.addToSamplingEvents( samplingEvent ); 571 } 572 if( assay ) { 573 assay.parent = study; 574 study.addToAssays( assay ); 575 } 576 } 577 578 if( sample ) { 579 if( subject ) sample.parentSubject = subject 580 if( samplingEvent ) sample.parentEvent = samplingEvent; 581 if( event ) { 582 def evGroup = new EventGroup(); 583 evGroup.addToEvents( event ); 584 if( subject ) evGroup.addToSubjects( subject ); 585 if( samplingEvent ) evGroup.addToSamplingEvents( samplingEvent ); 586 587 sample.parentEventGroup = evGroup; 588 } 589 590 if( assay ) assay.addToSamples( sample ); 591 } 592 } 593 188 594 /** 189 595 * Method to read data from a workbook and to import data into a two dimensional … … 258 664 */ 259 665 def getFieldNameInTableEditor(entity, field) { 666 def entityName = entity?.class.name[ entity?.class.name.lastIndexOf(".") + 1..-1] 667 260 668 if( field instanceof TemplateField ) 261 669 field = field.escapedName(); 262 670 263 return "entity_" + entity.getIdentifier() + "_" + field671 return entityName.toLowerCase() + "_" + entity.getIdentifier() + "_" + field 264 672 } 265 673 … … 562 970 } 563 971 } 972 973 /** 974 * Returns the preferred identifier field for a given entity or 975 * null if no preferred identifier is given 976 * @param entity TemplateEntity class 977 * @return The preferred identifier field or NULL if no preferred identifier is given 978 */ 979 public TemplateField givePreferredIdentifier( Class entity ) { 980 def allFields = entity.giveDomainFields(); 981 return allFields.find { it.preferredIdentifier } 982 } 564 983 565 984 // classes for fuzzy string matching
Note: See TracChangeset
for help on using the changeset viewer.