Changeset 1591
- Timestamp:
- Mar 7, 2011, 12:01:52 PM (11 years ago)
- Location:
- trunk/grails-app
- Files:
-
- 2 added
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/grails-app/controllers/dbnp/studycapturing/SimpleWizardController.groovy
r1581 r1591 68 68 // If the user clicks next, the study should be validated 69 69 if( event == "next" && validateObject( study ) ) { 70 toPage( "samples" ); 70 if( study.samples?.size() ) { 71 // This wizard can only handle simple studies. If the 72 // study is too complex, this wizard doesn't provide the 73 // possibility to edit samples/subjects etc. 74 if( checkStudySimplicity( study ) ) { 75 toPage( "existingSamples" ); 76 } else { 77 toPage( "complexStudy" ); 78 } 79 } else { 80 toPage( "samples" ); 81 } 82 71 83 return; 72 84 } … … 76 88 // Give the study to the user 77 89 [ study: study ] 90 } 91 92 /** 93 * Shows a page to mention that the study being edited is too complex 94 */ 95 def complexStudy = { 96 // Retrieve the correct study 97 study = getStudyInWizard( params ); 98 if( !study ) { 99 redirect( controller: 'simpleWizard', action: 'study' ); 100 return; 101 } 102 103 def event = getEvent(params); 104 105 // If any event on this page is triggered 106 if( event ) { 107 // Now determine what action to perform 108 if( event == "save" ) { 109 toPage( "save" ); 110 return; 111 } else if( event == "previous" ) { 112 toPage( "study" ); 113 return; 114 } 115 } 116 117 // Give the study and other data to the user 118 [ study: study ] 119 } 120 121 /** 122 * Shows the samples page 123 */ 124 def existingSamples = { 125 // Retrieve the correct study 126 study = getStudyInWizard( params ); 127 if( !study ) { 128 redirect( controller: 'simpleWizard', action: 'study' ); 129 return; 130 } 131 132 def event = getEvent(params); 133 134 // If any event on this page is triggered, we should save the entered data. 135 // If no event is triggered, the user gets here from another page. In that case, 136 // we don't set the values 137 if( event ) { 138 // Now determine what action to perform 139 if( event == "next" && handleExistingSamples( study, params )) { 140 // Only continue to the next page if the information entered is correct 141 toPage( "assays" ); 142 return; 143 } else if( event == "previous" ) { 144 // The user may go to the previous page, even if none of the data entered is OK. 145 toPage( "study" ); 146 return; 147 } else if( event == "update" && handleExistingSamples( study, params ) ) { 148 // The user may update the samples using excel. Before that, the sample date should be saved 149 toPage( "samples" ); 150 return; 151 } else if( event == "skip" ) { 152 // The user may skip the complete samples page 153 toPage( "assays" ); 154 return; 155 } 156 } 157 158 // Give the study and other data to the user 159 def records = importerService.getRecords( study ); 160 161 [ study: study, 162 records: records, 163 templateCombinations: records.templateCombination.unique(), 164 templates: [ 165 'Sample': Template.findAllByEntity( Sample.class ), 166 'Subject': Template.findAllByEntity( Subject.class ), 167 'Event': Template.findAllByEntity( Event.class ), 168 'SamplingEvent': Template.findAllByEntity( SamplingEvent.class ) 169 ], 170 encodedEntity: [ 171 'Sample': gdtService.encryptEntity( Sample.class.name ), 172 'Subject': gdtService.encryptEntity( Subject.class.name ), 173 'Event': gdtService.encryptEntity( Event.class.name ), 174 'SamplingEvent': gdtService.encryptEntity( SamplingEvent.class.name ) 175 ], 176 existingSampleForm: session.simpleWizard.existingSampleForm 177 ] 78 178 } 79 179 … … 102 202 } else if( event == "previous" ) { 103 203 // The user may go to the previous page, even if none of the data entered is OK. 104 toPage( "study" ); 204 if( study.samples?.size() ) 205 toPage( "existingSamples" ); 206 else 207 toPage( "study" ); 208 105 209 return; 106 210 } else if( event == "skip" ) { … … 112 216 113 217 // Give the study and other data to the user 114 [ study: study, sampleTemplates: Template.findAllByEntity( Sample.class ), encodedEntity: gdtService.encryptEntity( Sample.class.name ), sampleForm: session.simpleWizard.sampleForm ] 218 [ study: study, 219 templates: [ 220 'Sample': Template.findAllByEntity( Sample.class ), 221 'Subject': Template.findAllByEntity( Subject.class ), 222 'Event': Template.findAllByEntity( Event.class ), 223 'SamplingEvent': Template.findAllByEntity( SamplingEvent.class ) 224 ], 225 encodedEntity: [ 226 'Sample': gdtService.encryptEntity( Sample.class.name ), 227 'Subject': gdtService.encryptEntity( Subject.class.name ), 228 'Event': gdtService.encryptEntity( Event.class.name ), 229 'SamplingEvent': gdtService.encryptEntity( SamplingEvent.class.name ) 230 ], 231 sampleForm: session.simpleWizard.sampleForm ] 115 232 } 116 233 … … 134 251 // Now determine what action to perform 135 252 if( event == "next" && handleColumns( study, params ) ) { 253 136 254 // Only continue to the next page if the information entered is correct 137 255 if( session.simpleWizard.imported.numInvalidEntities > 0 ) { … … 142 260 fileService.delete( session.simpleWizard.sampleForm.importFile ); 143 261 262 session.simpleWizard.sampleForm = null 263 144 264 toPage( "assays" ); 145 265 } 146 266 return; 147 267 } else if( event == "previous" ) { 148 // T He user may go to the previous page, even if the data is not correct268 // The user may go to the previous page, even if the data is not correct 149 269 toPage( "samples" ); 150 270 return; 151 271 } 152 272 } 153 273 274 def templates = [:]; 275 def domainFields = [:] 276 277 session.simpleWizard.sampleForm.templateId.each { 278 templates[ it.key ] = it.value ? Template.get( it.value ) : null; 279 if( it.value ) { 280 domainFields[ it.key ] = templates[ it.key ].entity.giveDomainFields(); 281 } 282 } 283 154 284 // Give the study and other data to the user 155 285 [ study: study, 156 286 filename: session.simpleWizard.sampleForm.importFile, 157 template: Template.get( session.simpleWizard.sampleForm.templateId ), 287 templates: templates, 288 domainFields: domainFields, 158 289 excel: session.simpleWizard.excel] 159 290 } … … 180 311 if( session.simpleWizard.imported.numInvalidEntities == 0 ) { 181 312 // Only continue to the next page if the information entered is correct 182 313 183 314 // The import of the excel file has finished. Now delete the excelfile 184 if( session.simpleWizard.sampleForm.importFile ) 315 if( session.simpleWizard.sampleForm.importFile ) { 185 316 fileService.delete( session.simpleWizard.sampleForm.importFile ); 186 317 } 318 session.simpleWizard.sampleForm = null 319 187 320 toPage( "assays" ); 188 321 return; … … 203 336 } 204 337 338 // Create the right format for showing the data 339 def records = []; 340 session.simpleWizard.imported.data.each { row -> 341 def record = [:]; 342 row.each { object -> 343 // Attach template to session 344 if( object.template ) 345 attachTemplate( object.template ); 346 347 def entityName = object.class.name[ object.class.name.lastIndexOf( '.' ) + 1 .. -1 ] 348 record[ entityName ] = object; 349 } 350 records << record; 351 } 352 205 353 // Give the study and other data to the user 206 [ study: study, imported: session.simpleWizard.imported, rules: rules ] 354 println "Imported: " + session.simpleWizard.imported.failedCells; 355 356 [ study: study, imported: session.simpleWizard.imported, records: records, rules: rules ] 207 357 } 208 358 … … 217 367 return; 218 368 } 219 220 Assay assay 221 if( study.assays?.size() ) { 222 assay = study.assays[0]; 223 study.removeFromAssays( assay ); 224 } else { 225 assay = new Assay(); 226 } 227 369 370 Assay assay = getAssayInWizard( study ); 371 228 372 def event = getEvent(params); 229 373 … … 235 379 if( event == "skip" ) { 236 380 // The user may skip the complete assay page 381 382 // In case the user has created an assay before, it should only be kept if it 383 // existed before this step 384 if( session.simpleWizard.assay != null && !session.simpleWizard.assay.id ) { 385 session.simpleWizard.remove( "assay" ) 386 } 387 237 388 toPage( "overview" ); 238 389 return; 239 390 } else if( handleAssays( assay, params ) ) { 240 study.addToAssays( assay );241 242 391 // Now determine what action to perform 243 if( event == "next" && validateObject( study ) ) {392 if( event == "next" && validateObject( assay ) ) { 244 393 toPage( "overview" ); 245 394 return; 246 395 } else if( event == "previous" ) { 247 toPage( "samples" ) 396 if( study.samples?.size() ) 397 toPage( "existingSamples" ) 398 else 399 toPage( "samples" ); 400 248 401 return; 249 402 } … … 252 405 253 406 // Give the study to the user 254 [ study: study, assay: assay ]255 } 256 407 [ study: study, wizardAssay: assay ] 408 } 409 257 410 /** 258 411 * Shows an overview of the entered study … … 265 418 return; 266 419 } 267 420 421 Assay assay = getAssayInWizard(); 422 268 423 def event = getEvent(params); 269 424 … … 277 432 return; 278 433 } else if( event == "previous" ) { 279 toPage( "assay " )434 toPage( "assays" ) 280 435 return; 281 436 } … … 283 438 284 439 // Give the study to the user 285 [ study: study ]286 } 287 440 [ study: study, wizardAssay: assay ] 441 } 442 288 443 def save = { 289 444 // Retrieve the correct study … … 293 448 return; 294 449 } 295 296 // Make sure all samples are attached to all assays 297 study.assays.each { assay -> 298 assay.samples?.clear(); 299 study.samples.each { sample -> 300 assay.addToSamples( sample ) 301 } 302 } 303 304 // Save the study 305 if( study.save( flush: true ) ) { 450 451 def event = getEvent(params); 452 453 if( event && event == "previous" ) { 454 toPage( "assays" ); 455 return; 456 } 457 458 Assay newAssay = getAssayInWizard(); 459 460 if( newAssay && !study.assays?.contains( newAssay ) ) { 461 study.addToAssays( newAssay ); 462 } 463 464 //attachAndValidateEntities( study ); 465 466 // Save the study. The study must be merged if it is loaded from 467 // the database before, since otherwise it will raise 'lazy initialization' errors 468 // The validation is done in the other steps, so it is skipped here. 469 def success 470 471 if( 472 study.id ? 473 study.merge( validate: false, flush: true ) : 474 study.save( flush: true ) 475 ) { 476 // Make sure all samples are attached to all assays 477 study.assays.each { assay -> 478 def l = []+ assay.samples; 479 l.each { sample -> 480 if( sample ) 481 assay.removeFromSamples( sample ); 482 } 483 assay.samples?.clear(); 484 485 study.samples.each { sample -> 486 assay.addToSamples( sample ) 487 } 488 } 489 306 490 // Clear session 307 491 session.simpleWizard = null; 308 492 309 493 flash.message = "Your study is succesfully saved."; 494 success = true 310 495 } else { 311 496 flash.error = "An error occurred while saving your study"; 497 498 study.getErrors().each { println it } 499 500 // Remove the assay from the study again, since it is still available 501 // in the session 502 if( newAssay ) { 503 study.removeFromAssays( newAssay ); 504 newAssay.parent = study; 505 } 506 312 507 //validateObject( study ); 508 success = false 313 509 } 314 510 315 511 // Give the study to the user 316 [ study: study ]512 [ study: study, success: success ] 317 513 } 318 514 … … 353 549 handleStudyUsers(study, params, 'readers') 354 550 handleStudyUsers(study, params, 'writers') 355 551 356 552 return true 553 } 554 555 /** 556 * Handles the editing of existing samples 557 * @param study Study to update 558 * @param params Request parameter map 559 * @return True if everything went OK, false otherwise. An error message is put in flash.error 560 */ 561 def handleExistingSamples( study, params ) { 562 session.simpleWizard.existingSampleForm = params 563 flash.validationErrors = [:]; 564 565 def errors = false; 566 567 // iterate through objects; set field values and validate the object 568 def eventgroups = study.samples.parentEventGroup.findAll { it } 569 def events; 570 if( !eventgroups ) 571 events = [] 572 else 573 events = eventgroups.events?.getAt(0); 574 575 def objects = [ 576 'Sample': study.samples, 577 'Subject': study.samples.parentSubject.findAll { it }, 578 'SamplingEvent': study.samples.parentEvent.findAll { it }, 579 'Event': events.flatten().findAll { it } 580 ]; 581 objects.each { 582 def type = it.key; 583 def entities = it.value; 584 585 entities.each { entity -> 586 // iterate through entity fields 587 entity.giveFields().each() { field -> 588 def value = params.get( type.toLowerCase() + '_' + entity.getIdentifier() + '_' + field.escapedName()) 589 590 // set field value; name cannot be set to an empty value 591 if (field.name != 'name' || value) { 592 log.info "setting "+field.name+" to "+value 593 entity.setFieldValue(field.name, value) 594 } 595 } 596 597 // has the template changed? 598 def templateName = params.get(type.toLowerCase() + '_' + entity.getIdentifier() + '_template') 599 if (templateName && entity.template?.name != templateName) { 600 entity.template = Template.findByName(templateName) 601 } 602 603 // validate sample 604 if (!entity.validate()) { 605 errors = true; 606 flash.validationErrors << getHumanReadableErrors( entity ) 607 } 608 } 609 } 610 611 return !errors 357 612 } 358 613 … … 370 625 // still being in the temporary directory. 371 626 // This import step doesn't have to make that distinction, since all files remain in the temporary directory. 372 if( filename[0..8] == 'existing*' ) 627 if( filename == 'existing*' ) 628 filename = ''; 629 else if( filename[0..8] == 'existing*' ) 373 630 filename = filename[9..-1] 374 375 def templateId = params.long( 'template_id' ) 631 632 def sampleTemplateId = params.long( 'sample_template_id' ) 633 def subjectTemplateId = params.long( 'subject_template_id' ) 634 def eventTemplateId = params.long( 'event_template_id' ) 635 def samplingEventTemplateId = params.long( 'samplingEvent_template_id' ) 636 637 // These fields have been removed from the form, so will always contain 638 // their default value. The code however remains like this for future use. 376 639 int sheetIndex = (params.int( 'sheetindex' ) ?: 1 ) 377 640 int dataMatrixStart = (params.int( 'datamatrix_start' ) ?: 2 ) … … 381 644 session.simpleWizard.sampleForm = [ 382 645 importFile: filename, 383 templateId: templateId, 646 templateId: [ 647 'Sample': sampleTemplateId, 648 'Subject': subjectTemplateId, 649 'Event': eventTemplateId, 650 'SampingEvent': samplingEventTemplateId 651 ], 384 652 sheetIndex: sheetIndex, 385 653 dataMatrixStart: dataMatrixStart, … … 388 656 389 657 // Check whether the template exists 390 if (! templateId || !Template.get( templateId ) ){391 log.error ".simple study wizard not all fields are filled in "658 if (!sampleTemplateId || !Template.get( sampleTemplateId ) ){ 659 log.error ".simple study wizard not all fields are filled in: " + sampleTemplateId 392 660 flash.error = "No template was chosen. Please choose a template for the samples you provided." 393 661 return false … … 474 742 def handleColumns( study, params ) { 475 743 // Find actual Template object from the chosen template name 476 def template = Template.get(session.simpleWizard.sampleForm.templateId) 744 def templates = [:]; 745 session.simpleWizard.sampleForm.templateId.each { 746 templates[ it.key ] = it.value ? Template.get( it.value ) : null; 747 } 748 477 749 def headers = session.simpleWizard.excel.data.header; 478 750 … … 485 757 // Retrieve the chosen matches from the request parameters and put them into 486 758 // the headers-structure, for later reference 487 params.matches.index.each { columnindex, property -> 759 params.matches.index.each { columnindex, value -> 760 // Determine the entity and property by splitting it 761 def parts = value.toString().tokenize( "||" ); 762 763 def property 764 def entityName 765 if( parts.size() > 1 ) { 766 property = parts[ 1 ]; 767 entityName = "dbnp.studycapturing." + parts[ 0 ]; 768 } else if( parts.size() == 1 ) { 769 property = parts[ 0 ]; 770 entityName = headers[columnindex.toInteger()].entityclass.getName(); 771 } 772 488 773 // Create an actual class instance of the selected entity with the selected template 489 774 // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities 490 def entityClass = Class.forName( headers[columnindex.toInteger()].entityclass.getName(), true, this.getClass().getClassLoader()) 491 def entityObj = entityClass.newInstance(template: template) 492 775 def entityClass = Class.forName( entityName, true, this.getClass().getClassLoader()) 776 def entityObj = entityClass.newInstance(template: templates[ entityName[entityName.lastIndexOf( '.' ) + 1..-1] ]) 777 778 headers[ columnindex.toInteger() ].entityclass = entityClass 779 493 780 // Store the selected property for this column into the column map for the ImporterService 494 781 headers[columnindex.toInteger()].property = property … … 501 788 502 789 //if it's an identifier set the mapping column true or false 503 entity Obj.giveFields().each {790 entityClass.giveDomainFields().each { 504 791 headers[columnindex.toInteger()].identifier = ( it.preferredIdentifier && (it.name == property) ) 505 792 } … … 507 794 508 795 // Import the workbook and store the table with entity records and store the failed cells 509 def (table, failedcells) = importerService.importData(session.simpleWizard.sampleForm.templateId, 796 println "Importing samples for study " + study + " (" + study.id + ")"; 797 798 def imported = importerService.importOrUpdateDataBySampleIdentifier(templates, 510 799 session.simpleWizard.excel.workbook, 511 800 session.simpleWizard.excel.sheetIndex - 1, 512 801 session.simpleWizard.excel.dataMatrixStart - 1, 513 session.simpleWizard.excel.data.header) 802 session.simpleWizard.excel.data.header, 803 study, 804 true // Also create entities for which no data is imported but where templates were chosen 805 ); 806 807 def table = imported.table 808 def failedcells = imported.failedCells 514 809 515 810 session.simpleWizard.imported = [ 516 517 518 811 data: table, 812 failedCells: failedcells 813 ]; 519 814 520 815 // loop through all entities to validate them and add them to failedcells if an error occurs … … 522 817 def errors = []; 523 818 524 // Remove all samples 525 study.samples?.clear(); 526 819 // Add all samples 527 820 table.each { record -> 528 821 record.each { entity -> 822 if( entity ) { 823 // Determine entity class and add a parent 824 def preferredIdentifier = importerService.givePreferredIdentifier( entity.class ); 825 def equalClosure = { it.getFieldValue( preferredIdentifier.name ) == entity.getFieldValue( preferredIdentifier.name ) } 826 827 entity.parent = study 828 829 switch( entity.class ) { 830 case Sample: 831 if( preferredIdentifier && !study.samples?.find( equalClosure ) ) { 832 study.addToSamples( entity ); 833 } 834 break; 835 case Subject: 836 if( preferredIdentifier && !study.subjects?.find( equalClosure ) ) { 837 study.addToSubjects( entity ); 838 } 839 break; 840 case Event: 841 if( preferredIdentifier && !study.events?.find( equalClosure ) ) { 842 study.addToEvents( entity ); 843 } 844 break; 845 case SamplingEvent: 846 if( preferredIdentifier && !study.samplingEvents?.find( equalClosure ) ) { 847 study.addToSamplingEvents( entity ); 848 } 849 break; 850 } 851 852 if (!entity.validate()) { 853 numInvalidEntities++; 854 855 // Add this field to the list of failed cells, in order to give the user feedback 856 failedcells = addNonValidatingCells( failedcells, entity ) 857 858 // Also create a full list of errors 859 errors += getHumanReadableErrors( entity ); 860 } 861 } 862 } 863 } 864 865 session.simpleWizard.imported.numInvalidEntities = numInvalidEntities + failedcells?.size(); 866 session.simpleWizard.imported.errors = errors; 867 868 return true 869 } 870 871 /** 872 * Handles the update of the edited fields by the user 873 * @param study Study to update 874 * @param params Request parameter map 875 * @return True if everything went OK, false otherwise. An error message is put in flash.error. 876 * The field session.simpleWizard.imported.numInvalidEntities reflects the number of 877 * entities that still have errors, and should be fixed before saving. The errors for those entities 878 * are saved into session.simpleWizard.imported.errors 879 */ 880 def handleMissingFields( study, params ) { 881 def numInvalidEntities = 0; 882 def errors = []; 883 884 // Check which fields failed previously 885 def failedCells = session.simpleWizard.imported.failedCells 886 887 session.simpleWizard.imported.data.each { table -> 888 table.each { entity -> 889 def invalidFields = 0 890 891 // Set the fields for this entity by retrieving values from the params 892 entity.giveFields().each { field -> 893 def fieldName = importerService.getFieldNameInTableEditor( entity, field ); 894 895 if( params[ fieldName ] == "#invalidterm" ) { 896 // If the value '#invalidterm' is chosen, the user hasn't fixed anything, so this field is still incorrect 897 invalidFields++; 898 } else { 899 if( field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM || field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST ) { 900 // If this field is an ontologyterm field or a stringlist field, the value has changed, so remove the field from 901 // the failedCells list 902 importerService.removeFailedCell( failedCells, entity, field ) 903 } 904 905 // Update the field, regardless of the type of field 906 entity.setFieldValue(field.name, params[ fieldName ] ) 907 } 908 } 909 529 910 // Determine entity class and add a parent 530 entity.parent = study 531 study.addToSamples( entity ); 532 533 if (!entity.validate() ) {911 entity.parent = study; 912 913 // Try to validate the entity now all fields have been set. If it fails, return an error 914 if (!entity.validate() || invalidFields) { 534 915 numInvalidEntities++; 535 916 536 917 // Add this field to the list of failed cells, in order to give the user feedback 537 failed cells = addNonValidatingCells( failedcells, entity )918 failedCells = addNonValidatingCells( failedCells, entity ) 538 919 539 920 // Also create a full list of errors 540 921 errors += getHumanReadableErrors( entity ); 541 } 542 } 543 } 922 } else { 923 importerService.removeFailedCell( failedCells, entity ) 924 } 925 } // end of record 926 } // end of table 544 927 545 928 session.simpleWizard.imported.numInvalidEntities = numInvalidEntities; … … 548 931 return true 549 932 } 933 934 /** 935 * Handles assay input 936 * @param study Study to update 937 * @param params Request parameter map 938 * @return True if everything went OK, false otherwise. An error message is put in flash.error 939 */ 940 def handleAssays( assay, params ) { 941 // did the study template change? 942 if (params.get('template') && assay.template?.name != params.get('template')) { 943 // set the template 944 assay.template = Template.findByName(params.remove('template')) 945 } 946 947 // does the study have a template set? 948 if (assay.template && assay.template instanceof Template) { 949 // yes, iterate through template fields 950 assay.giveFields().each() { 951 // and set their values 952 assay.setFieldValue(it.name, params.get(it.escapedName())) 953 } 954 } 955 956 // Save the assay in session 957 session.simpleWizard.assay = assay; 958 959 return true 960 } 550 961 551 962 /** 552 * Handles the update of the edited fields by the user 553 * @param study Study to update 554 * @param params Request parameter map 555 * @return True if everything went OK, false otherwise. An error message is put in flash.error. 556 * The field session.simpleWizard.imported.numInvalidEntities reflects the number of 557 * entities that still have errors, and should be fixed before saving. The errors for those entities 558 * are saved into session.simpleWizard.imported.errors 559 */ 560 def handleMissingFields( study, params ) { 561 def numInvalidEntities = 0; 562 def errors = []; 563 564 // Remove all samples before adding them again 565 study.samples?.clear(); 566 567 // Check which fields failed previously 568 def failedCells = session.simpleWizard.imported.failedCells 569 570 session.simpleWizard.imported.data.each { table -> 571 table.each { entity -> 572 def invalidFields = 0 573 574 // Set the fields for this entity by retrieving values from the params 575 entity.giveFields().each { field -> 576 def fieldName = importerService.getFieldNameInTableEditor( entity, field ); 577 578 if( params[ fieldName ] == "#invalidterm" ) { 579 // If the value '#invalidterm' is chosen, the user hasn't fixed anything, so this field is still incorrect 580 invalidFields++; 581 } else { 582 if( field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM || field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST ) { 583 // If this field is an ontologyterm field or a stringlist field, the value has changed, so remove the field from 584 // the failedCells list 585 importerService.removeFailedCell( failedCells, entity, field ) 586 } 587 588 // Update the field, regardless of the type of field 589 entity.setFieldValue(field.name, params[ fieldName ] ) 590 } 591 } 592 593 // Determine entity class and add a parent 594 entity.parent = study; 595 study.addToSamples( entity ); 596 597 // Try to validate the entity now all fields have been set. If it fails, return an error 598 if (!entity.validate() || invalidFields) { 599 numInvalidEntities++; 600 601 // Add this field to the list of failed cells, in order to give the user feedback 602 failedCells = addNonValidatingCells( failedCells, entity ) 603 604 // Also create a full list of errors 605 errors += getHumanReadableErrors( entity ); 606 } else { 607 importerService.removeFailedCell( failedCells, entity ) 608 } 609 } // end of record 610 } // end of table 611 612 session.simpleWizard.imported.numInvalidEntities = numInvalidEntities; 613 session.simpleWizard.imported.errors = errors; 614 615 return true 616 } 617 618 /** 619 * Handles assay input 620 * @param study Study to update 621 * @param params Request parameter map 622 * @return True if everything went OK, false otherwise. An error message is put in flash.error 623 */ 624 def handleAssays( assay, params ) { 625 // did the study template change? 626 if (params.get('template') && assay.template?.name != params.get('template')) { 627 // set the template 628 assay.template = Template.findByName(params.remove('template')) 629 } 630 631 // does the study have a template set? 632 if (assay.template && assay.template instanceof Template) { 633 // yes, iterate through template fields 634 assay.giveFields().each() { 635 // and set their values 636 assay.setFieldValue(it.name, params.get(it.escapedName())) 637 } 638 } 639 640 return true 641 } 642 643 /** 644 * Validates an object and puts human readable errors in validationErrors variable 645 * @param entity Entity to validate 646 * @return True iff the entity validates, false otherwise 647 */ 648 protected boolean validateObject( def entity ) { 649 if( !entity.validate() ) { 650 flash.validationErrors = getHumanReadableErrors( entity ) 651 return false; 652 } 653 return true; 654 } 655 656 963 * Checks whether the given study is simple enough to be edited using this controller. 964 * 965 * The study is simple enough if the samples, subjects, events and samplingEvents can be 966 * edited as a flat table. That is: 967 * - Every subject belongs to 0 or 1 eventgroup 968 * - Every eventgroup belongs to 0 or 1 sample 969 * - Every eventgroup has 0 or 1 subjects, 0 or 1 event and 0 or 1 samplingEvents 970 * - If a sample belongs to an eventgroup: 971 * - If that eventgroup has a samplingEvent, that same samplingEvent must also be 972 * the sampling event that generated this sample 973 * - If that eventgroup has a subject, that same subject must also be the subject 974 * from whom the sample was taken 975 * 976 * @param study Study to check 977 * @return True if the study can be edited by this controller, false otherwise 978 */ 979 def checkStudySimplicity( study ) { 980 def simplicity = true; 981 982 if( !study ) 983 return false 984 985 if( study.eventGroups ) { 986 study.eventGroups.each { eventGroup -> 987 // Check for simplicity of eventgroups: only 0 or 1 subject, 0 or 1 event and 0 or 1 samplingEvent 988 if( eventGroup.subjects?.size() > 1 || eventGroup.events?.size() > 1 || eventGroup.samplingEvents?.size() > 1 ) { 989 flash.message = "One or more eventgroups contain multiple subjects or events." 990 simplicity = false; 991 } 992 993 // Check whether this eventgroup only belongs to (max) 1 sample 994 def numSamples = 0; 995 study.samples.each { sample -> 996 // If no id is given for the eventGroup, it has been entered in this wizard, but 997 // not yet saved. In that case, it is always OK 998 if( eventGroup.id && sample.parentEventGroup?.id == eventGroup.id ) 999 numSamples++; 1000 } 1001 1002 if( numSamples > 1 ) { 1003 flash.message = "One or more eventgroups belong to multiple samples." 1004 simplicity = false; 1005 } 1006 } 1007 1008 if( !simplicity ) return false; 1009 1010 // Check whether subject only belong to zero or one event group 1011 if( study.subjects ) { 1012 study.subjects.each { subject -> 1013 def numEventGroups = 0 1014 study.eventGroups.each { eventGroup -> 1015 // If no id is given for the subject, it has been entered in this wizard, but 1016 // not yet saved. In that case, it is always OK 1017 if( subject.id && eventGroup.subjects[0]?.id == subject.id ) 1018 numEventGroups++ 1019 } 1020 1021 if( numEventGroups > 1 ) { 1022 flash.message = "One or more subjects belong to multiple eventgroups." 1023 simplicity = false; 1024 } 1025 } 1026 } 1027 1028 if( !simplicity ) return false; 1029 1030 // Check whether the samples that belong to an eventgroup have the right parentObjects 1031 study.samples.each { sample -> 1032 if( sample.parentEventGroup ) { 1033 // If no id is given for the subject, it has been entered in this wizard, but 1034 // not yet saved. In that case, it is always OK 1035 if( sample.parentSubject && sample.parentSubject.id) { 1036 if( !sample.parentEventGroup.subjects || sample.parentEventGroup.subjects[0]?.id != sample.parentSubject.id ) { 1037 flash.message = "The structure of the eventgroups of one or more samples is too complex" 1038 simplicity = false; 1039 } 1040 } 1041 1042 // If no id is given for the sampling event, it has been entered in this wizard, but 1043 // not yet saved. In that case, it is always OK 1044 if( sample.parentEvent && sample.parentEvent.id) { 1045 if( !sample.parentEventGroup.samplingEvents || sample.parentEventGroup.samplingEvents[0]?.id != sample.parentEvent.id ) { 1046 flash.message = "The structure of the eventgroups of one or more samples is too complex" 1047 simplicity = false; 1048 } 1049 } 1050 } 1051 } 1052 1053 if( !simplicity ) return false; 1054 } 1055 1056 return simplicity; 1057 } 1058 1059 def attachAndValidateEntities( def study ) { 1060 if( !session.simpleWizard?.imported?.data ) 1061 return 1062 1063 def table = session.simpleWizard.imported.data 1064 def numInvalidEntities = 0; 1065 1066 // Add all samples 1067 table.each { record -> 1068 println record*.class 1069 record.each { entity -> 1070 if( entity ) { 1071 if( entity.validate() ) { 1072 println "Saving: " + entity + " (" + entity.class.name + ")" 1073 println study.samples; 1074 println study.subjects; 1075 1076 // Determine entity class and add a parent 1077 def preferredIdentifier = importerService.givePreferredIdentifier( entity.class ); 1078 def equalClosure = { it.getFieldValue( preferredIdentifier.name ) == entity.getFieldValue( preferredIdentifier.name ) } 1079 1080 //entity.parent = study 1081 switch( entity.class ) { 1082 case Sample: 1083 if( preferredIdentifier && !study.samples?.find( equalClosure ) ) { 1084 study.addToSamples( entity ); 1085 } 1086 break; 1087 case Subject: 1088 if( preferredIdentifier && !study.subjects?.find( equalClosure ) ) { 1089 study.addToSubjects( entity ); 1090 } 1091 break; 1092 case Event: 1093 if( preferredIdentifier && !study.events?.find( equalClosure ) ) { 1094 study.addToEvents( entity ); 1095 } 1096 break; 1097 case SamplingEvent: 1098 if( preferredIdentifier && !study.samplingEvents?.find( equalClosure ) ) { 1099 study.addToSamplingEvents( entity ); 1100 } 1101 break; 1102 } 1103 1104 entity.save(); 1105 1106 } else { 1107 numInvalidEntities++; 1108 } 1109 } 1110 } 1111 } 1112 1113 return numInvalidEntities == 0; 1114 } 1115 1116 /** 1117 * Validates an object and puts human readable errors in validationErrors variable 1118 * @param entity Entity to validate 1119 * @return True iff the entity validates, false otherwise 1120 */ 1121 protected boolean validateObject( def entity ) { 1122 if( !entity.validate() ) { 1123 flash.validationErrors = getHumanReadableErrors( entity ) 1124 return false; 1125 } 1126 return true; 1127 } 1128 657 1129 /** 658 1130 * Adds all fields of this entity that have given an error when validating to the failedcells list … … 667 1139 entity.getErrors().getFieldErrors().each { error -> 668 1140 String field = error.getField(); 1141 669 1142 def mc = importerService.findMappingColumn( session.simpleWizard.excel.data.header, field ); 670 1143 def mcInstance = new MappingColumn( name: field, entityClass: Sample.class, index: -1, property: field.toLowerCase(), templateFieldType: entity.giveFieldType( field ) ); … … 681 1154 return failedcells 682 1155 } 683 684 685 1156 686 1157 … … 698 1169 if( sheetIndex > workbook.getNumberOfSheets() ) { 699 1170 log.error ".simple study wizard Sheet index is too high: " + sheetIndex + " / " + workbook.getNumberOfSheets(); 700 flash.error = " The sheet number you provided is too high. The provided excel sheet has only " + workbook.getNumberOfSheets() + " sheet(s).";1171 flash.error = "Your excel sheet contains too few excel sheets. The provided excel sheet has only " + workbook.getNumberOfSheets() + " sheet(s)."; 701 1172 return false 702 1173 } … … 709 1180 if( headerRow > numRows ) { 710 1181 log.error ".simple study wizard Header row number is incorrect: " + headerRow + " / " + numRows; 711 flash.error = " The header row number you provided is too high. Please provide a number equal to or below " + numRows;1182 flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below"; 712 1183 return false 713 1184 } … … 715 1186 if( dataMatrixStart > numRows ) { 716 1187 log.error ".simple study wizard Data row number is incorrect: " + dataMatrixStart + " / " + numRows; 717 flash.error = " The data row number you provided is too high. Please provide a number equal to or below " + numRows;1188 flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below"; 718 1189 return false 719 1190 } … … 756 1227 Study s = Study.get( id ); 757 1228 1229 if( !s ) { 1230 flash.error = "No study found with given id"; 1231 return null; 1232 } 758 1233 if( !s.canWrite( authenticationService.getLoggedInUser() ) ) { 759 1234 flash.error = "No authorization to edit this study." … … 764 1239 } 765 1240 1241 /** 1242 * Attach a template to the session 1243 * @param t 1244 * @return 1245 */ 1246 protected attachTemplate( Template t ) { 1247 if( t && !t.isAttached() ) { 1248 t.attach(); 1249 1250 t.fields.each { field -> 1251 if( field && !field.isAttached() ) 1252 field.attach(); 1253 1254 field.listEntries?.each { entry -> 1255 if( entry && !entry.isAttached() ) 1256 entry.attach(); 1257 } 1258 field.ontologies?.each { entry -> 1259 if( entry && !entry.isAttached() ) 1260 entry.attach(); 1261 } 1262 } 1263 } 1264 } 1265 766 1266 /** 767 1267 * Retrieves the study that is saved in the wizard, … … 773 1273 if( params.wizard && session.simpleWizard && session.simpleWizard.study ) { 774 1274 // The user came here by clicking previous or a link on another page. Use the existing study 775 return session.simpleWizard.study; 1275 Study s = session.simpleWizard.study; 1276 1277 if( s.id && !s.isAttached() ) { 1278 s.attach(); 1279 } 1280 1281 s.samples?.each { 1282 if( it && it.id && !it.isAttached() ) 1283 it.attach(); 1284 1285 attachTemplate( it.template ) 1286 } 1287 s.subjects?.each { 1288 if( it && it.id && !it.isAttached() ) 1289 it.attach(); 1290 1291 attachTemplate( it.template ) 1292 } 1293 s.events?.each { 1294 if( it && it.id && !it.isAttached() ) 1295 it.attach(); 1296 1297 attachTemplate( it.template ) 1298 } 1299 1300 s.samplingEvents?.each { 1301 if( it && it.id && !it.isAttached() ) 1302 it.attach(); 1303 1304 attachTemplate( it.template ) 1305 } 1306 s.assays?.each { 1307 if( it && it.id && !it.isAttached() ) 1308 it.attach(); 1309 1310 attachTemplate( it.template ) 1311 } 1312 1313 return s; 776 1314 } else { 777 1315 // The user didn't get here from the wizard or no study is found 1316 return null; 1317 } 1318 } 1319 1320 /** 1321 * Retrieves the assay to edit in the wizard 1322 * @param s Study that the assay will be in 1323 * @return Assay object (may be empty) 1324 */ 1325 protected Assay getAssayInWizard( Study study = null ) { 1326 if( session.simpleWizard && session.simpleWizard.assay ) { 1327 Assay a = session.simpleWizard.assay; 1328 1329 if( a.id && !a.isAttached() ) { 1330 a.attach(); 1331 1332 attachTemplate( a.template ); 1333 } 1334 1335 return a; 1336 } else if( study ) { 1337 // The user came on the assay page for the first time 1338 if( study.assays?.size() ) { 1339 def assay = study.assays[0]; 1340 1341 return assay; 1342 } else { 1343 return new Assay( parent: study ); 1344 } 1345 } else { 778 1346 return null; 779 1347 } … … 802 1370 return errors 803 1371 } 804 805 1372 } -
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 -
trunk/grails-app/views/simpleWizard/assays.gsp
r1581 r1591 38 38 <input type="hidden" name="wizard" value="true" /> 39 39 <input type="hidden" name="event" value="refresh" /> 40 41 40 <af:templateElement name="template" description="Template" 42 value="${ assay?.template}" entity="${dbnp.studycapturing.Assay}"41 value="${wizardAssay?.template}" entity="${dbnp.studycapturing.Assay}" 43 42 addDummy="true" onChange="if(\$( this ).val() != '') { submitForm( 'assays' ); }"> 44 43 Choose the type of assay you would like to perform. … … 46 45 </af:templateElement> 47 46 48 <g:if test="${ assay}">49 <af:templateElements ignore="externalassayid" entity="${ assay}" />47 <g:if test="${wizardAssay}"> 48 <af:templateElements ignore="externalassayid" entity="${wizardAssay}" /> 50 49 </g:if> 51 50 -
trunk/grails-app/views/simpleWizard/columns.gsp
r1581 r1591 43 43 <tr class="matchWith"> 44 44 <g:each in="${excel.data.header}" var="mappingcolumn" status="i"> 45 <% 46 def selectedValue; 47 if( mappingcolumn.entityclass?.name && mappingcolumn.property ) 48 selectedValue = mappingcolumn.entityclass.name[ mappingcolumn.entityclass.name.lastIndexOf( "." ) + 1 .. -1 ] + mappingcolumn.property; 49 %> 45 50 <td> 46 51 <g:set var="selected" value="${mappingcolumn.property}"/> 47 <importer:propertyChooser name="matches" mappingcolumn="${mappingcolumn}" matchvalue="${mappingcolumn.name}" 48 selected="${selected}" fuzzymatching="true" treshold="0.8" template_id="${template.id}" "allfieldtypes="true"/> 52 <% /* Put a select box with template fields of multiple entities */ %> 53 <select name="matches.index.${mappingcolumn.index}" style="font-size: 10px;"> 54 <option value="dontimport">Don't import</option> 55 <g:each in="${templates}" var="entityTemplates"> 56 <g:if test="${entityTemplates.value}"> 57 <optgroup label="${entityTemplates.key}"> 58 <% 59 def allFields = domainFields[ entityTemplates.key ] + entityTemplates.value?.fields; 60 %> 61 <g:each in="${allFields}" var="field"> 62 <% 63 def value = entityTemplates.key + "||" + field.name 64 def selected = ( value == selectedValue ); 65 %> 66 <option value="${value}" <g:if test="${selected}">selected="selected"</g:if>> 67 ${field.name} <g:if test="${field.preferredIdentifier}">[identifier]</g:if> 68 69 </g:each> 70 </optgroup> 71 </g:if> 72 </g:each> 73 </select> 49 74 </td> 50 75 </g:each> -
trunk/grails-app/views/simpleWizard/missingFields.gsp
r1581 r1591 47 47 48 48 <div class="wizard" id="wizard"> 49 <div class="tableEditor"> 50 <g:set var="showHeader" value="${true}"/> 51 <g:each status="index" var="table" in="${imported.data}"> 52 <g:each status="i" var="entity" in="${table}"> 53 <g:if test="${showHeader}"> 54 <g:set var="showHeader" value="${false}"/> 55 <div class="header"> 56 <div class="firstColumn"></div> 57 <af:templateColumnHeaders entity="${entity}" class="column" /> 58 </div> 59 <input type="hidden" name="entity" value="${entity.getClass().getName()}"> 60 </g:if> 61 <div class="row"> 49 <g:set var="showHeader" value="${true}" /> 50 <g:set var="previousTemplate" value=""/> 51 <div class="tableEditor"> 52 <g:each var="record" in="${records}"> 53 <g:if test="${showHeader}"> 54 <g:set var="showHeader" value="${false}" /> 55 <div class="header"> 62 56 <div class="firstColumn"></div> 63 <af:templateColumns id="${entity.hashCode()}" entity="${entity}" template="${entity.template}" name="entity_${entity.getIdentifier()}" class="column" subject="${entity.hashCode()}" addDummy="true" /> 64 </div> 65 </g:each> 66 </g:each> 67 </div> 68 <div class="sliderContainer"> 69 <div class="slider"></div> 70 </div> 57 <g:each var="entity" in="${record}"> 58 <g:if test="${entity.value}"> 59 <af:templateColumnHeaders entity="${entity.value}" class="column" columnWidths="[Name:100]"/> 60 </g:if> 61 </g:each> 62 </div> 63 </g:if> 64 <div class="row"> 65 <div class="firstColumn"></div> 66 <g:each var="entity" in="${record}"> 67 <g:if test="${entity.value}"> 68 <af:templateColumns id="${entity.value.hashCode()}" name="${entity.key.toLowerCase()}_${entity.value.getIdentifier()}" template="${entity.value.template}" class="column" id="1" entity="${entity.value}" addDummy="true" subject="${entity.hashCode()}" /> 69 </g:if> 70 </g:each> 71 </div> 72 </g:each> 73 </div> 74 <div class="sliderContainer"> 75 <div class="slider" ></div> 76 </div> 71 77 <div> 72 78 </g:form> -
trunk/grails-app/views/simpleWizard/overview.gsp
r1581 r1591 89 89 90 90 <div class="element"> 91 <div class="description">Sample template</div>${study.samples?.getAt(0)?.template?.name} 91 <div class="description">Samples</div> 92 93 <g:if test="${study.samples?.size()}"> 94 <g:each in="${study.samples.template.unique()}" var="currentSampleTemplate" status="j"> 95 <g:if test="${currentSampleTemplate}"> 96 <g:if test="${j > 0}">,</g:if> 97 <%=study.samples.findAll { return it.template == currentSampleTemplate; }.size()%> 98 ${currentSampleTemplate.name} 99 </g:if> 100 </g:each> 101 </g:if> 102 <g:else> 103 - 104 </g:else> 92 105 </div> 93 <div class="element"> 94 <div class="description"># samples</div>${study.samples?.size()} 95 </div> 96 <div class="element"> 97 <div class="description">Assay template</div>${study.assays?.getAt(0)?.template?.name} 98 </div> 106 107 <br /> 108 <g:if test="${wizardAssay}"> 109 <div class="element"> 110 <div class="description">Assay template</div>${wizardAssay.template?.name} 111 </div> 112 </g:if> 99 113 100 114 <br clear="all" /> -
trunk/grails-app/views/simpleWizard/samples.gsp
r1581 r1591 27 27 <input type="hidden" name="wizard" value="true" /> 28 28 <input type="hidden" name="event" value="refresh" /> 29 30 <g:if test="${study.samples?.size()}"> 31 <p>Current samples</p> 32 <g:each in="${study.samples}" var="sample"> 33 ${sample.name}<br /> 34 </g:each> 35 36 <p class="options"> 37 <a href="#" onClick="$('#samplesDialog').dialog('open'); return false;" class="add">Add samples</a> 38 <a href="#" onClick="$('#samplesDialog').dialog('open'); return false;" class="update">Update samples</a> 39 </p> 40 41 <% /* If samples are already present, the dialog should not be opened by default */ %> 42 <script type="text/javascript"> 43 $( '#samplesDialog' ).dialog({ 44 title : "Add/update samples", 45 autoOpen: false, 46 width : 800, 47 height : 400, 48 modal : true, 49 position: "center", 50 buttons : { 51 Add : function() { 52 //addUser(element_id); 53 $(this).dialog("close"); 54 }, 55 Update : function() { 56 //addUser(element_id); 57 $(this).dialog("close"); 58 }, 59 Close : function() { 60 $(this).dialog("close"); 61 } 62 }, 63 close : function() { 64 /* closeFunc(this); */ 65 } 66 }); 67 </script> 68 </g:if> 29 69 30 <div id="samplesDialog"> 70 31 <span class="info"> 71 32 <span class="title">Import sample data</span> 72 You can import your Excel data to the server by choosing a file from your local harddisk in the form below. 33 You can import your Excel data to the server by choosing a file from your local harddisk in the form below. The excel sheet should contain 34 data on the first sheet, and the sheet should contain one row with headers. 73 35 </span> 74 36 … … 83 45 </tr> 84 46 <tr> 85 <td width="100px">86 Use data from sheet:87 </td>88 <td width="100px">89 <g:select name="sheetindex" from="${1..25}" value="${sampleForm?.sheetIndex}"/>90 </td>91 </tr>92 <tr>93 <td width="100px">94 Columnheader starts at row:95 </td>96 <td width="100px">97 <g:select name="headerrow" from="${1..10}" value="${sampleForm?.headerRow}"/>98 </td>99 </tr>100 <tr>101 <td width="100px">102 Data starts at row:103 </td>104 <td width="100px">105 <g:select name="datamatrix_start" from="${2..10}" value="${sampleForm?.dataMatrixStart}"/>106 </td>107 </tr>108 <tr>109 47 <td> 110 48 <div id="datatemplate">Choose type of sample template:</div> 111 49 </td> 112 50 <td> 113 <g:select rel="template" entity="${encodedEntity }" name="template_id" optionKey="id" optionValue="name" from="${sampleTemplates}" value="${sampleForm?.templateId}"/>51 <g:select rel="template" entity="${encodedEntity.Sample}" name="sample_template_id" optionKey="id" optionValue="name" from="${templates.Sample}"/> 114 52 </td> 115 53 </tr> 116 54 </table> 117 118 55 </div> 119 56 -
trunk/grails-app/views/simpleWizard/save.gsp
r1581 r1591 22 22 </g:if> 23 23 24 ${study} 25 26 <g:form class="simpleWizard" name="saved" action="saved" controller="simpleWizard"> 24 <g:form class="simpleWizard" name="saved" action="save" controller="simpleWizard"> 27 25 <input type="hidden" name="wizard" value="true" /> 28 26 <input type="hidden" name="event" value="refresh" /> 29 27 30 28 <p class="options"> 31 <g:link class="view" controller="study" action="show" id="${study.id}">View study</g:link> 32 <g:link class="edit" controller="simpleWizard" action="study" id="${study.id}">Edit study</g:link> 33 <g:link class="restart" controller="simpleWizard" action="study">Add another study</g:link> 29 <g:if test="${success}"> 30 <g:link class="view" controller="study" action="show" id="${study.id}">View study</g:link> 31 <g:link class="edit" controller="simpleWizard" action="study" id="${study.id}">Edit study</g:link> 32 <g:link class="restart" controller="simpleWizard" action="study">Add another study</g:link> 33 </g:if> 34 <g:else> 35 <a href="#" onClick="submitForm( 'saved', 'previous' ); return false;" class="previous">Previous</a> 36 </g:else> 34 37 </p> 35 38 -
trunk/grails-app/views/studyWizard/pages/_samples.gsp
r1461 r1591 36 36 37 37 <g:if test="${study.samples}"> 38 <g:if test="${study.samples.findAll{!it .template}.size()}">38 <g:if test="${study.samples.findAll{!it?.template}.size()}"> 39 39 <h1>Samples that still need to have a template assigned</h1> 40 40 <div class="tableEditor"> … … 48 48 <g:set var="previousTemplate" value=""/> 49 49 <g:each var="sample" in="${study.samples}"> 50 <g:if test="${!sample .template}">50 <g:if test="${!sample?.template}"> 51 51 <div class="row"> 52 52 <div class="firstColumn"> </div> 53 53 <div class="column"> 54 <g:if test="${previousTemplate != sample.parentEvent .template.name}">55 <g:set var="previousTemplate" value="${sample.parentEvent .template.name}"/>56 ${sample.parentEvent .template.name}54 <g:if test="${previousTemplate != sample.parentEvent?.template?.name}"> 55 <g:set var="previousTemplate" value="${sample.parentEvent?.template?.name}"/> 56 ${sample.parentEvent?.template?.name} 57 57 <div class="helpIcon"></div> 58 58 <div class="helpContent"> 59 <h1>${sample.parentEvent .template.name}</h1>59 <h1>${sample.parentEvent?.template?.name}</h1> 60 60 <h2>Template Fields:</h2> 61 <g:each var="field" in="${sample.parentEvent .giveFields()}">61 <g:each var="field" in="${sample.parentEvent?.giveFields()}"> 62 62 ${field.name[0].toUpperCase() + field.name.substring(1)}<br/> 63 63 </g:each> … … 66 66 </div> 67 67 <div class="column"> 68 ${sample.parentSubject .name}68 ${sample.parentSubject?.name} 69 69 <div class="helpIcon"></div> 70 70 <div class="helpContent"> 71 <h1>${sample.parentSubject .template.name}</h1>71 <h1>${sample.parentSubject?.template?.name}</h1> 72 72 <h2>Template Fields:</h2> 73 <g:each var="field" in="${sample.parentSubject .giveFields()}">73 <g:each var="field" in="${sample.parentSubject?.giveFields()}"> 74 74 ${field.name[0].toUpperCase() + field.name.substring(1)}<br/> 75 75 </g:each> … … 109 109 <div class="firstColumn"></div> 110 110 <div class="column"> 111 <g:if test="${previousTemplate != sample.parentEvent .template.name}">112 <g:set var="previousTemplate" value="${sample.parentEvent .template.name}"/>113 ${sample.parentEvent .template.name}111 <g:if test="${previousTemplate != sample.parentEvent?.template?.name}"> 112 <g:set var="previousTemplate" value="${sample.parentEvent?.template?.name}"/> 113 ${sample.parentEvent?.template?.name} 114 114 <div class="helpIcon"></div> 115 115 <div class="helpContent"> 116 <h1>${sample.parentEvent .template.name}</h1>116 <h1>${sample.parentEvent?.template?.name}</h1> 117 117 <h2>Template Fields:</h2> 118 <g:each var="field" in="${sample.parentEvent .giveFields()}">118 <g:each var="field" in="${sample.parentEvent?.giveFields()}"> 119 119 ${field.name[0].toUpperCase() + field.name.substring(1)}<br/> 120 120 </g:each> … … 123 123 </div> 124 124 <div class="column"> 125 ${sample.parentSubject .name}125 ${sample.parentSubject?.name} 126 126 <div class="helpIcon"></div> 127 127 <div class="helpContent"> 128 <h1>${sample.parentSubject .template.name}</h1>128 <h1>${sample.parentSubject?.template?.name}</h1> 129 129 <h2>Template Fields:</h2> 130 <g:each var="field" in="${sample.parentSubject .giveFields()}">130 <g:each var="field" in="${sample.parentSubject?.giveFields()}"> 131 131 ${field.name[0].toUpperCase() + field.name.substring(1)}<br/> 132 132 </g:each> … … 134 134 </div> 135 135 <div class="column"> 136 <af:templateSelect name="template_${sample.getIdentifier()}" entity="${dbnp.studycapturing.Sample}" value="${sample .template}" addDummy="true" tableEditorChangeEvent="switchTemplate(element);" />136 <af:templateSelect name="template_${sample.getIdentifier()}" entity="${dbnp.studycapturing.Sample}" value="${sample?.template}" addDummy="true" tableEditorChangeEvent="switchTemplate(element);" /> 137 137 </div> 138 138 <af:templateColumns name="sample_${sample.getIdentifier()}" class="column" id="1" entity="${sample}"/>
Note: See TracChangeset
for help on using the changeset viewer.