root/trunk/grails-app/controllers/dbnp/importer/ImporterController.groovy @ 1489

Revision 1489, 17.9 KB (checked in by t.w.abma@…, 3 years ago)

- improved missing values in stringlist fields, still to fix: make cell identifier really unique

  • Property svn:keywords set to Rev Author Date
RevLine 
[1277]1package dbnp.importer
[1419]2
[1426]3import dbnp.studycapturing.*
[1461]4import org.dbnp.gdt.*
[1277]5import grails.converters.JSON
[1358]6import org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib
[1387]7import grails.plugins.springsecurity.Secured
[1416]8
[215]9/**
[1277]10 * Wizard Controller
[215]11 *
[1419]12 * @author Jeroen Wesbeek
13 * @since 20101206
[215]14 *
15 * Revision information:
16 * $Rev$
17 * $Author$
18 * $Date$
19 */
[1418]20@Secured(['IS_AUTHENTICATED_REMEMBERED'])
[1277]21class ImporterController {
[1419]22        // the pluginManager is used to check if the Grom
23        // plugin is available so we can 'Grom' development
24        // notifications to the unified notifications daemon
25        // (see http://www.grails.org/plugin/grom)
26        def pluginManager
[1421]27        def authenticationService
[1419]28        def fileService
29        def ImporterService
30        def validationTagLib = new ValidationTagLib()
[1443]31        def GdtService
[215]32
[1419]33        /**
34         * index method, redirect to the webflow
35         * @void
36         */
37        def index = {
38                // Grom a development message
39                if (pluginManager.getGrailsPlugin('grom')) "redirecting into the webflow".grom()
[1413]40
[1419]41                /**
42                 * Do you believe it in your head?
43                 * I can go with the flow
44                 * Don't say it doesn't matter (with the flow) matter anymore
45                 * I can go with the flow (I can go)
46                 * Do you believe it in your head?
47                 */
48                redirect(action: 'pages')
49        }
[354]50
[1419]51        /**
52         * WebFlow definition
53         * @void
54         */
55        def pagesFlow = {
56                // start the flow
57                onStart {
58                        // Grom a development message
59                        if (pluginManager.getGrailsPlugin('grom')) "entering the WebFlow".grom()
[215]60
[1419]61                        // define variables in the flow scope which is availabe
62                        // throughout the complete webflow also have a look at
63                        // the Flow Scopes section on http://www.grails.org/WebFlow
64                        //
65                        // The following flow scope variables are used to generate
66                        // wizard tabs. Also see common/_tabs.gsp for more information
67                        flow.page = 0
68                        flow.pages = [
69                                [title: 'Import file'],
[1421]70                                [title: 'Assign properties'],
71                                [title: 'Check imported data'],
[1419]72                                //[title: 'Imported'],
[1421]73                                [title: 'Done']
[1419]74                        ]
75                        flow.cancel = true;
76                        flow.quickSave = true;
[1171]77
[1419]78                        success()
79                }
[215]80
[1419]81                // render the main wizard page which immediately
82                // triggers the 'next' action (hence, the main
83                // page dynamically renders the study template
84                // and makes the flow jump to the study logic)
85                mainPage {
86                        render(view: "/importer/index")
87                        onRender {
88                                // Grom a development message
89                                if (pluginManager.getGrailsPlugin('grom')) "rendering the main Ajaxflow page (index.gsp)".grom()
[989]90
[1419]91                                // let the view know we're in page 1
92                                flow.page = 1
93                                success()
94                        }
95                        on("next").to "pageOne"
96                }
[1191]97
[1419]98                // File import and entitie template selection page
99                pageOne {
100                        render(view: "_page_one")
101                        onRender {
102                                log.info ".entering import wizard"
103                                // Grom a development message
104                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial: pages/_page_one.gsp".grom()
[1387]105
[1419]106                                flow.page = 1
[1421]107                                flow.studies = Study.findAllWhere(owner: authenticationService.getLoggedInUser())
[1358]108
[1419]109                                success()
110                        }
[1469]111
112            on("refresh") {
[1489]113                flash.importer_params = params               
[1469]114                                success()
115                        }.to "pageOne"
116
[1419]117                        on("next") {
118                                flash.wizardErrors = [:]
[1388]119
[1419]120                                // Study selected?
121                                flow.importer_study = (params.study) ? Study.get(params.study.id.toInteger()) : null
[1171]122
[1419]123                                // Trying to import data into an existing study?
124                                if (flow.importer_study)
[1421]125                                        if (flow.importer_study.canWrite(authenticationService.getLoggedInUser())) {
[1419]126                                                if (fileImportPage(flow, params)) {
127                                                        success()
128                                                } else {
129                                                        log.error ".importer wizard not all fields are filled in"
130                                                        this.appendErrorMap(['error': "Not all fields are filled in, please fill in or select all fields"], flash.wizardErrors)
131                                                        error()
132                                                }
133                                        } else {
134                                                log.error ".importer wizard wrong permissions"
135                                                this.appendErrorMap(['error': "You don't have the right permissions"], flash.wizardErrors)
[1171]136
[1419]137                                                error()
138                                        }
139                                else {
140                                        if (fileImportPage(flow, params)) {
141                                                success()
142                                        } else {
143                                                log.error ".importer wizard not all fields are filled in"
144                                                this.appendErrorMap(['error': "Not all fields are filled in, please fill in or select all fields"], flash.wizardErrors)
145                                                error()
146                                        }
147                                }
[215]148
[1419]149                                // put your bussiness logic (if applicable) in here
150                        }.to "pageTwo"
151                }
[417]152
[1419]153                // Property to column assignment page
154                pageTwo {
155                        render(view: "_page_two")
156                        onRender {
157                                log.info ".import wizard properties page"
158                                // Grom a development message
159                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial: pages/_page_two.gsp".grom()
[417]160
[1419]161                                flow.page = 2
162                                success()
163                        }
164                        on("next") {
[1472]165                                if (propertiesPage(flow, flash, params)) {
[1419]166                                        success()
167                                } else {
168                                        log.error ".import wizard, properties are set wrong"
169                                        error()
170                                }
171                        }.to "pageThree"
172                        on("previous").to "pageOne"
173                }
[492]174
[1419]175                // Mapping page
176                pageThree {
177                        render(view: "_page_three")
178                        onRender {
179                                log.info ".import wizard mapping page"
180                                // Grom a development message
181                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_three.gsp".grom()
[215]182
[1419]183                                flow.page = 3
184                                success()
185                        }
186                        on("refresh") {
187                                success()
188                        }.to "pageThree"
189                        on("next") {
190                                if (mappingsPage(flow, flash, params)) {
191                                        flow.page = 4
192                                        success()
193                                } else {
194                                        log.error ".import wizard mapping error, could not validate all entities"
195                                        error()
196                                }
197                        }.to "save"
198                        on("previous").to "pageTwo"
199                }
200
201                // Imported data overview page
202                pageFour {
203                        render(view: "_page_four")
204                        onRender {
205                                // Grom a development message
206                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_four.gsp".grom()
207
208                                flow.page = 4
209                                success()
210                        }
211                        on("next") {
212                                if (importedPage(flow, params)) {
213                                        flow.page = 4
214                                        success()
215                                } else {
216                                        log.error ".import wizard imported error, something went wrong showing the imported entities"
217                                        error()
218                                }
219                        }.to "save"
220                        on("previous").to "pageThree"
221                }
222
223                // Save the imported data
224                save {
225                        action {
226                                // here you can validate and save the
227                                // instances you have created in the
228                                // ajax flow.
229                                // Grom a development message
230                                if (pluginManager.getGrailsPlugin('grom')) ".persisting instances to the database...".grom()
231
232                                if (saveEntities(flow, params)) {
[1417]233                                        //if (ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata)) {
[1422]234                                        //log.error ".import wizard succesfully persisted all entities"
[1419]235                                        //def session = sessionFactory.getCurrentSession()
236                                        //session.clear()
237                                        success()
238                                } else {
239                                        log.error ".import wizard, could not save entities:\n" + e.dump()
240                                        flow.page = 4
241                                        error()
242                                }
243                        }
244                        on("error").to "error"
245                        on(Exception).to "error"
246                        on("success").to "finalPage"
247                }
[485]248
[1419]249                // render errors
250                error {
251                        render(view: "_error")
252                        onRender {
253                                // Grom a development message
254                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_error.gsp".grom()
[215]255
[1419]256                                // set page to 4 so that the navigation
257                                // works (it is disabled on the final page)
258                                flow.page = 4
259                        }
260                        on("next").to "save"
261                        on("previous").to "pageFour"
262                }
263
264                // last wizard page
265                finalPage {
266                        render(view: "_final_page")
267                        onRender {
268                                // Grom a development message
269                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_final_page.gsp".grom()
270                                success()
271                        }
272                        onEnd {
[1416]273                                // clean flow scope
274                                flow.clear()
275                        }
[1419]276                }
277        }
[215]278
[1419]279        /**
280         * Return templates which belong to a certain entity type
281         *
282         * @param entity entity name string (Sample, Subject, Study et cetera)
283         * @return JSON object containing the found templates
284         */
285        def ajaxGetTemplatesByEntity = {
286                // fetch all templates for a specific entity
[1443]287                def templates = Template.findAllByEntity(GdtService.getInstanceByEntity(params.entity.decodeURL()))
[1277]288
[1443]289
[1419]290                // render as JSON
291                render templates as JSON
292        }
[485]293
[1419]294        /**
295         * Handle the file import page.
296         *
297         * @param Map LocalAttributeMap (the flow scope)
298         * @param Map GrailsParameterMap (the flow parameters = form data)
299         * @returns boolean true if correctly validated, otherwise false
300         */
301        boolean fileImportPage(flow, params) {
302                def importedfile = fileService.get(params['importfile'])
303                //fileService.delete(YourFile)
[632]304
[1419]305                if (params.entity && params.template_id && importedfile.exists()) {
306                        // create a workbook instance of the file
307                        session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
[1215]308
[1419]309                        def selectedentities = []
[1215]310
[1443]311                        def entityName = GdtService.decryptEntity(params.entity.decodeURL())
312                        def entityClass = GdtService.getInstanceByEntityName(entityName)
[1215]313
[1419]314                        // Initialize some session variables
315                        //flow.importer_workbook = wb // workbook object must be serialized for this to work
[1083]316
[1419]317                        flow.importer_template_id = params.template_id
318                        flow.importer_sheetindex = params.sheetindex.toInteger() - 1 // 0 == first sheet
319                        flow.importer_datamatrix_start = params.datamatrix_start.toInteger() - 1 // 0 == first row
320                        flow.importer_headerrow = params.headerrow.toInteger()
[215]321
[1419]322                        // Get the header from the Excel file using the arguments given in the first step of the wizard
323                        flow.importer_header = ImporterService.getHeader(session.importer_workbook,
324                                flow.importer_sheetindex,
325                                flow.importer_headerrow,
326                                flow.importer_datamatrix_start,
327                                entityClass)
[485]328
[1419]329                        // Initialize 'selected entities', used to show entities above the columns
330                        flow.importer_header.each {
331                                selectedentities.add([name: entityName, columnindex: it.key.toInteger()])
332                        }
[245]333
[1419]334                        flow.importer_selectedentities = selectedentities
[251]335
[1419]336                        session.importer_datamatrix = ImporterService.getDatamatrix(
337                                session.importer_workbook, flow.importer_header,
338                                flow.importer_sheetindex,
339                                flow.importer_datamatrix_start,
340                                5)
[251]341
[1419]342                        flow.importer_templates = Template.get(flow.importer_template_id)
343                        flow.importer_allfieldtypes = "true"
[245]344
[1419]345                        return true
346                }
347        }
[215]348
[1419]349        /**
350         * Handle the property mapping page.
351         *
352         * @param Map LocalAttributeMap (the flow scope)
353         * @param Map GrailsParameterMap (the flow parameters = form data)
354         * @returns boolean true if correctly validated, otherwise false
355         */
[1472]356        boolean propertiesPage(flow, flash, params) {
357        flash.wizardErrors = [:]
358       
[1419]359                // Find actual Template object from the chosen template name
360                def template = Template.get(flow.importer_template_id)
[1277]361
[1419]362                params.columnproperty.index.each { columnindex, property ->
363                        // Create an actual class instance of the selected entity with the selected template
364                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
365                        def entityClass = Class.forName(flow.importer_header[columnindex.toInteger()].entity.getName(), true, this.getClass().getClassLoader())
366                        def entityObj = entityClass.newInstance(template: template)
[245]367
[1419]368                        // Store the selected property for this column into the column map for the ImporterService
369                        flow.importer_header[columnindex.toInteger()].property = property
[660]370
[1419]371                        // Look up the template field type of the target TemplateField and store it also in the map
372                        flow.importer_header[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
[534]373
[1419]374                        // Is a "Don't import" property assigned to the column?
375                        flow.importer_header[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false
[660]376
[1419]377                        //if it's an identifier set the mapping column true or false
378                        entityObj.giveFields().each {
379                                (it.preferredIdentifier && (it.name == property)) ? flow.importer_header[columnindex.toInteger()].identifier = true : false
380                        }
381                }
[1358]382
[1419]383                // Import the workbook and store the table with entity records and store the failed cells
384                def (table, failedcells) = ImporterService.importData(flow.importer_template_id,
385                        session.importer_workbook,
386                        flow.importer_sheetindex,
387                        flow.importer_datamatrix_start,
388                        flow.importer_header)
389
390                flow.importer_importeddata = table
[1472]391
392        // loop through all entities to validate them and add them to wizardErrors flash when invalid
393        table.each { record ->
394            record.each { entity ->
395                if (!entity.validate()) {
396                                this.appendErrors(entity, flash.wizardErrors, 'entity_' + entity.getIdentifier() + '_')
397                }
398            }
399        }
400
401        flow.importer_failedcells = failedcells
402
[1419]403                return true
404        }
405
406        /**
407         * Handle the mapping page.
408         *
409         * @param Map LocalAttributeMap (the flow scope)
410         * @param Map GrailsParameterMap (the flow parameters = form data)
411         * @returns boolean true if correctly validated, otherwise false
412         */
413        boolean mappingsPage(flow, flash, params) {
414                flash.wizardErrors = [:]
415                flow.importer_invalidentities = 0
416
417                flow.importer_importeddata.each { table ->
418                        table.each { entity ->
[1489]419                                def invalidfields = 0
[1419]420
421                                // Set the fields for this entity by retrieving values from the params
422                                entity.giveFields().each { field ->
423                                        // field of type ontology and value "#invalidterm"?
[1456]424                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
[1419]425                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
426                                        ) {
[1489]427                                                invalidfields++
[1419]428                                        } else
[1456]429                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
[1419]430                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
[1489]431                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)
432                        println "removedcellontology" + entity
[1419]433                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
434                                        }
435                                        else
[1489]436
437                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
438                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
439                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)
440                        println "removedcellstringlist" + entity.getIdentifier()
[1419]441                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
[1489]442                    } else
443                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
444                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
445                                        ) {
446                                                invalidfields++
447                                        } else
448
449                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
[1419]450                                }
451
452                                // Determine entity class and add a parent (defined as Study in first step of wizard)
453                                switch (entity.getClass()) {
454                                        case [Subject, Sample, Event]: entity.parent = flow.importer_study
455                                }
456
457                                // Try to validate the entity now all fields have been set
[1489]458                                if (!entity.validate() || invalidfields) {
[1419]459                                        flow.importer_invalidentities++
460
461                                        // add errors to map
462                                        this.appendErrors(entity, flash.wizardErrors, "entity_" + entity.getIdentifier() + "_")
463
[1311]464                                        entity.errors.getAllErrors().each() {
465                                                log.error ".import wizard imported validation error:" + it
466                                        }
[1419]467                                } else {
468                                        removeFailedCell(flow.importer_failedcells, entity)
469                                } // end else if
[1311]470
[1419]471                        } // end of record
472                } // end of table
[669]473
[1489]474        println "invalidentities="+flow.importer_invalidentities
475
[1419]476                return (flow.importer_invalidentities == 0) ? true : false
477        } // end of method
[660]478
[1419]479        /**
480         * @param failedcell failed ontology cells
481         * @param entity entity to remove from the failedcells list
482         */
[1489]483        def removeFailedCell(failedcells, entity, field) {
[1419]484                // Valid entity, remove it from failedcells
485                failedcells.each { record ->
486                        def tempimportcells = []
[1381]487
[1419]488                        record.importcells.each { cell ->
[1489]489               // println "cell_entity=" + cell.value
490               // println "fieldname=" + field.name
491
[1419]492                                // remove the cell from the failed cells session
493                                if (cell.entityidentifier != entity.getIdentifier()) {
494                                        //record.removeFromImportcells(cell)
495                                        tempimportcells.add(cell)
496                                }
497                        }
[1367]498
[1419]499                        record.importcells = tempimportcells
500                        // } // end of importcells
501                } // end of failedcells
502        }
[449]503
[1419]504        /**
505         * Handle the imported entities page.
506         *
507         * @param Map LocalAttributeMap (the flow scope)
508         * @param Map GrailsParameterMap (the flow parameters = form data)
509         * @returns boolean true if correctly validated, otherwise false
510         */
511        boolean importedPage(flow, params) {
512                return true
513        }
514
515        boolean saveEntities(flow, params) {
516                //def (validatedSuccesfully, updatedEntities, failedToPersist) =
517                //try {
[1422]518                ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata, authenticationService, log)
[1419]519
520                //}
521                //catch (Exception e) {
[1416]522//                log.error ".import wizard saveEntities error\n" + e.dump()
523//            }
[449]524
[1419]525                //flow.importer_validatedsuccesfully = validatedSuccesfully
526                //flow.importer_failedtopersist = failedToPersist
527                //flow.imported_updatedentities = updatedEntities
528                //flow.importer_totalrows = flow.importer_importeddata.size
529                //flow.importer_referer = ""
[1358]530
[1419]531                return true
532        }
533
534        /**
[1358]535         * append errors of a particular object to a map
536         * @param object
537         * @param map linkedHashMap
538         * @void
539         */
540        def appendErrors(object, map) {
541                this.appendErrorMap(getHumanReadableErrors(object), map)
[1419]542        }
[1358]543
544        def appendErrors(object, map, prepend) {
545                this.appendErrorMap(getHumanReadableErrors(object), map, prepend)
546        }
547
[1419]548        /**
[1358]549         * append errors of one map to another map
550         * @param map linkedHashMap
551         * @param map linkedHashMap
552         * @void
553         */
554        def appendErrorMap(map, mapToExtend) {
555                map.each() {key, value ->
[1419]556                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
[1358]557                }
558        }
559
560        def appendErrorMap(map, mapToExtend, prepend) {
561                map.each() {key, value ->
562                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
563                }
564        }
565
[1419]566        /**
[1358]567         * transform domain class validation errors into a human readable
568         * linked hash map
569         * @param object validated domain class
570         * @return object  linkedHashMap
571         */
572        def getHumanReadableErrors(object) {
573                def errors = [:]
574                object.errors.getAllErrors().each() { error ->
[1411]575                        // error.codes.each() { code -> println code }           
[1358]576
577                        // generally speaking g.message(...) should work,
578                        // however it fails in some steps of the wizard
579                        // (add event, add assay, etc) so g is not always
580                        // availably. Using our own instance of the
581                        // validationTagLib instead so it is always
582                        // available to us
[1419]583                        errors[error.getArguments()[0]] = validationTagLib.message(error: error)
[1358]584                }
585
586                return errors
587        }
[215]588}
Note: See TracBrowser for help on using the browser.