source: trunk/grails-app/controllers/dbnp/importer/ImporterController.groovy @ 1536

Last change on this file since 1536 was 1536, checked in by t.w.abma@…, 9 years ago
  • fixed refresh of first page in import wizard, to fix: TemplateFileField? "*existing" check
  • Property svn:keywords set to Rev Author Date
File size: 23.1 KB
Line 
1package dbnp.importer
2
3import dbnp.studycapturing.*
4import org.dbnp.gdt.*
5import grails.converters.JSON
6import org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib
7import grails.plugins.springsecurity.Secured
8
9/**
10 * Wizard Controller
11 *
12 * @author Jeroen Wesbeek
13 * @since 20101206
14 *
15 * Revision information:
16 * $Rev: 1536 $
17 * $Author: t.w.abma@umcutrecht.nl $
18 * $Date: 2011-02-17 15:52:50 +0000 (do, 17 feb 2011) $
19 */
20@Secured(['IS_AUTHENTICATED_REMEMBERED'])
21class ImporterController {
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
27        def authenticationService
28        def fileService
29        def ImporterService
30        def validationTagLib = new ValidationTagLib()
31        def GdtService
32
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()
40
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        }
50
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()
60
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'],
70                                [title: 'Assign properties'],
71                                [title: 'Check imported data'],
72                                //[title: 'Imported'],
73                                [title: 'Done']
74                        ]
75                        flow.cancel = true;
76                        flow.quickSave = true;
77
78                        success()
79                }
80
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()
90
91                                // let the view know we're in page 1
92                                flow.page = 1
93                                success()
94                        }
95                        on("next").to "pageOne"
96                }
97
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()
105
106                                flow.page = 1
107                                flow.studies = Study.findAllWhere(owner: authenticationService.getLoggedInUser())
108                flow.importer_fuzzymatching="false"
109
110                                success()
111                        }
112
113            on("refresh") {
114                println params.importfile
115
116                if (params.entity) {
117                    flash.importer_datatemplates = Template.findAllByEntity(GdtService.getInstanceByEntity(params.entity.decodeURL()))
118                }
119               
120                flash.importer_params = params
121
122                                success()
123                        }.to "pageOne"
124
125                        on("next") {
126                                flash.wizardErrors = [:]
127                flash.importer_params = params
128
129                if (params.entity) {
130                    flash.importer_datatemplates = Template.findAllByEntity(GdtService.getInstanceByEntity(params.entity.decodeURL()))
131                }
132
133                                // Study selected?
134                                flow.importer_study = (params.study) ? Study.get(params.study.id.toInteger()) : null
135
136                                // Trying to import data into an existing study?
137                                if (flow.importer_study)
138                                        if (flow.importer_study.canWrite(authenticationService.getLoggedInUser())) 
139                                                fileImportPage(flow, flash, params) ? success() : error()                                       
140                                        else {
141                                                log.error ".importer wizard wrong permissions"
142                                                this.appendErrorMap(['error': "You don't have the right permissions"], flash.wizardErrors)
143                                                error()
144                                        }
145                                else {
146                                        fileImportPage(flow, flash, params) ? success() : error()
147                                }
148
149                                // put your bussiness logic (if applicable) in here
150                        }.to "pageTwo"
151                }
152
153                // Property to column assignment page
154                pageTwo {
155                        render(view: "_page_two")
156                        onRender {
157                                log.info ".import wizard properties page"
158               
159                def template = Template.get(flow.importer_template_id)
160
161                                // Grom a development message
162                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial: pages/_page_two.gsp".grom()
163
164                flow.importer_importmappings = ImportMapping.findAllByTemplate(template)
165
166                                flow.page = 2
167                                success()
168                        }
169            on("refresh") {
170                def template = Template.get(flow.importer_template_id)
171                flow.importer_importmappings = ImportMapping.findAllByTemplate(template)
172
173                // a name was given to the current property mapping, try to store it
174                if (params.mappingname) {
175                    flash.importer_columnproperty = params.columnproperty
176                    propertiesSaveImportMappingPage(flow, flash, params)                   
177                } else // trying to load an existing import mapping
178                if (params.importmapping_id) {
179                    propertiesLoadImportMappingPage(flow, flash, params)
180                }
181               
182                if (params.fuzzymatching == "true")
183                    flow.importer_fuzzymatching="true" else
184                    flow.importer_fuzzymatching="false"
185
186                success()
187                        }.to "pageTwo"
188
189                        on("next") {
190                                if (propertiesPage(flow, flash, params)) {
191                                        success()
192                                } else {
193                                        log.error ".import wizard, properties are set wrong"
194                                        error()
195                                }
196                        }.to "pageThree"
197                        on("previous").to "pageOne"
198                }
199
200                // Mapping page
201                pageThree {
202                        render(view: "_page_three")
203                        onRender {
204                                log.info ".import wizard mapping page"
205                                // Grom a development message
206                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_three.gsp".grom()
207
208                                flow.page = 3
209                                success()
210                        }
211                        on("refresh") {
212                                success()
213                        }.to "pageThree"
214                        on("next") {
215                                if (mappingsPage(flow, flash, params)) {
216                                        flow.page = 4
217                                        success()
218                                } else {
219                                        log.error ".import wizard mapping error, could not validate all entities"
220                                        error()
221                                }
222                        }.to "save"
223                        on("previous").to "pageTwo"
224                }
225
226                // Imported data overview page
227                pageFour {
228                        render(view: "_page_four")
229                        onRender {
230                                // Grom a development message
231                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_four.gsp".grom()
232
233                                flow.page = 4
234                                success()
235                        }
236                        on("next") {
237                                if (importedPage(flow, params)) {
238                                        flow.page = 4
239                                        success()
240                                } else {
241                                        log.error ".import wizard imported error, something went wrong showing the imported entities"
242                                        error()
243                                }
244                        }.to "save"
245                        on("previous").to "pageThree"
246                }
247
248                // Save the imported data
249                save {
250                        action {
251                                // here you can validate and save the
252                                // instances you have created in the
253                                // ajax flow.
254                                // Grom a development message
255                                if (pluginManager.getGrailsPlugin('grom')) ".persisting instances to the database...".grom()
256
257                                if (saveEntities(flow, params)) {
258                                        //if (ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata)) {
259                                        //log.error ".import wizard succesfully persisted all entities"
260                                        //def session = sessionFactory.getCurrentSession()
261                                        //session.clear()
262                                        success()
263                                } else {
264                                        log.error ".import wizard, could not save entities:\n" + e.dump()
265                                        flow.page = 4
266                                        error()
267                                }
268                        }
269                        on("error").to "error"
270                        on(Exception).to "error"
271                        on("success").to "finalPage"
272                }
273
274                // render errors
275                error {
276                        render(view: "_error")
277                        onRender {
278                                // Grom a development message
279                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_error.gsp".grom()
280
281                                // set page to 4 so that the navigation
282                                // works (it is disabled on the final page)
283                                flow.page = 4
284                        }
285                        on("next").to "save"
286                        on("previous").to "pageFour"
287                }
288
289                // last wizard page
290                finalPage {
291                        render(view: "_final_page")
292                        onRender {
293                                // Grom a development message
294                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_final_page.gsp".grom()
295                                success()
296                        }
297                        onEnd {
298                                // clean flow scope
299                                flow.clear()
300                        }
301                }
302        }
303
304    def propertiesManager = {
305        render (view:"common/_propertiesManager")
306    }
307
308        /**
309         * Return templates which belong to a certain entity type
310         *
311         * @param entity entity name string (Sample, Subject, Study et cetera)
312         * @return JSON object containing the found templates
313         */
314        def ajaxGetTemplatesByEntity = {
315                // fetch all templates for a specific entity
316                def templates = Template.findAllByEntity(GdtService.getInstanceByEntity(params.entity.decodeURL()))
317
318
319                // render as JSON
320                render templates as JSON
321        }
322
323        /**
324         * Handle the file import page.
325         *
326         * @param Map LocalAttributeMap (the flow scope)
327         * @param Map GrailsParameterMap (the flow parameters = form data)
328         * @returns boolean true if correctly validated, otherwise false
329         */
330        boolean fileImportPage(flow, flash, params) {
331                def importedfile = fileService.get(params['importfile'])
332                //fileService.delete(YourFile)
333
334        if (importedfile.exists()) {
335            try {
336                session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
337            } catch (Exception e) {
338                log.error ".importer wizard could not load file: " + e
339                this.appendErrorMap(['error': "Wrong file (format), the importer requires an Excel file as input"], flash.wizardErrors)
340                return false
341            }
342        }
343
344                if (params.entity && params.template_id) {           
345                       
346            try {
347                session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
348            } catch (Exception e) {
349                log.error ".importer wizard could not load file: " + e
350                this.appendErrorMap(['error': "Excel file required as input"], flash.wizardErrors)
351                return false
352            }
353
354                        def selectedentities = []
355
356                        def entityName = GdtService.decryptEntity(params.entity.decodeURL())
357                        def entityClass = GdtService.getInstanceByEntityName(entityName)
358
359                        // Initialize some session variables
360                        //flow.importer_workbook = wb // workbook object must be serialized for this to work
361
362                        flow.importer_template_id = params.template_id
363                        flow.importer_sheetindex = params.sheetindex.toInteger() - 1 // 0 == first sheet
364                        flow.importer_datamatrix_start = params.datamatrix_start.toInteger() - 1 // 0 == first row
365                        flow.importer_headerrow = params.headerrow.toInteger()
366            flow.importer_entityclass = entityClass
367
368                        // Get the header from the Excel file using the arguments given in the first step of the wizard
369                        flow.importer_header = ImporterService.getHeader(session.importer_workbook,
370                                flow.importer_sheetindex,
371                                flow.importer_headerrow,
372                                flow.importer_datamatrix_start,
373                                entityClass)
374
375                        session.importer_datamatrix = ImporterService.getDatamatrix(
376                                session.importer_workbook, flow.importer_header,
377                                flow.importer_sheetindex,
378                                flow.importer_datamatrix_start,
379                                5)
380
381                        flow.importer_templates = Template.get(flow.importer_template_id)
382                        flow.importer_allfieldtypes = "true"
383
384                        return true
385                }
386
387
388        log.error ".importer wizard not all fields are filled in"
389        this.appendErrorMap(['error': "Not all fields are filled in, please fill in or select all fields"], flash.wizardErrors)
390        return false
391        }
392
393    /**
394     * Load an existing import mapping
395     *
396     * @param Map LocalAttributeMap (the flow scope)
397         * @param Map GrailsParameterMap (the flow parameters = form data)
398         * @returns boolean true if correctly validated, otherwise false
399         */
400    boolean propertiesLoadImportMappingPage(flow, flash, params) {
401        def im = ImportMapping.get(params.importmapping_id.toInteger())
402        im.refresh()
403
404        im.mappingcolumns.each { mappingcolumn ->
405            //def mc = new MappingColumn()
406            //mc.properties = mappingcolumn.properties
407
408            flow.importer_header[mappingcolumn.index.toInteger()] = mappingcolumn           
409        }
410    }
411
412    /**
413     * Save the properties as an import mapping.
414     *
415     * @param Map LocalAttributeMap (the flow scope)
416         * @param Map GrailsParameterMap (the flow parameters = form data)
417         * @returns boolean true if correctly validated, otherwise false
418         */
419    boolean propertiesSaveImportMappingPage(flow, flash, params) {
420        flash.wizardErrors = [:]
421        def isPreferredIdentifier = false
422
423                // Find actual Template object from the chosen template name
424                def template = Template.get(flow.importer_template_id)
425       
426        // Create new ImportMapping instance and persist it
427        def im = new ImportMapping(name:params.mappingname, entity: flow.importer_entityclass, template:template).save()       
428
429                params.columnproperty.index.each { columnindex, property ->
430                        // Create an actual class instance of the selected entity with the selected template
431                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities                     
432                        //def entityClass = GdtService.getInstanceByEntityName(flow.importer_header[columnindex.toInteger()].entity.getName())           
433            def entityObj = flow.importer_entityclass.newInstance(template:template)
434
435            def dontimport = (property == "dontimport") ? true : false
436
437            // Loop through all fields and find the preferred identifier
438            entityObj.giveFields().each {               
439                                isPreferredIdentifier = (it.preferredIdentifier && (it.name == property)) ? true : false
440                        }
441
442            // Create new MappingColumn instance
443            def mc = new MappingColumn (importmapping:im,
444                                        name: flow.importer_header[columnindex.toInteger()].name,
445                                        property:property,
446                                        index:columnindex,
447                                        entityclass:flow.importer_entityclass,
448                                        templatefieldtype:entityObj.giveFieldType(property),
449                                        dontimport: dontimport,
450                                        identifier:isPreferredIdentifier)
451
452            // Save mappingcolumn
453            if (mc.validate()) {
454                im.addToMappingcolumns(mc)               
455            }
456            else {
457                mc.errors.allErrors.each {
458                    println it
459                }
460            }
461
462            // Save importmapping
463            if (im.validate()) {
464                try {
465                    im.save(flush:true)
466                } catch (Exception e) {
467                    //getNextException
468                    log.error "importer wizard save importmapping error: " + e
469                }
470            }
471            else {
472                im.errors.allErrors.each {
473                    println it
474                }
475            }
476           
477                }
478    }
479
480        /**
481         * Handle the property mapping page.
482         *
483         * @param Map LocalAttributeMap (the flow scope)
484         * @param Map GrailsParameterMap (the flow parameters = form data)
485         * @returns boolean true if correctly validated, otherwise false
486         */
487        boolean propertiesPage(flow, flash, params) {
488        flash.wizardErrors = [:]
489       
490                // Find actual Template object from the chosen template name
491                def template = Template.get(flow.importer_template_id)
492
493                params.columnproperty.index.each { columnindex, property ->
494                        // Create an actual class instance of the selected entity with the selected template
495                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
496                        def entityClass = Class.forName(flow.importer_header[columnindex.toInteger()].entityclass.getName(), true, this.getClass().getClassLoader())
497                        def entityObj = entityClass.newInstance(template: template)
498
499                        // Store the selected property for this column into the column map for the ImporterService
500                        flow.importer_header[columnindex.toInteger()].property = property
501
502                        // Look up the template field type of the target TemplateField and store it also in the map
503                        flow.importer_header[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
504
505                        // Is a "Don't import" property assigned to the column?
506                        flow.importer_header[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false
507
508                        //if it's an identifier set the mapping column true or false
509                        entityObj.giveFields().each {
510                                (it.preferredIdentifier && (it.name == property)) ? flow.importer_header[columnindex.toInteger()].identifier = true : false
511                        }
512                }
513
514                // Import the workbook and store the table with entity records and store the failed cells
515                def (table, failedcells) = ImporterService.importData(flow.importer_template_id,
516                        session.importer_workbook,
517                        flow.importer_sheetindex,
518                        flow.importer_datamatrix_start,
519                        flow.importer_header)
520
521                flow.importer_importeddata = table
522
523        // loop through all entities to validate them and add them to wizardErrors flash when invalid
524        table.each { record ->
525            record.each { entity ->
526                if (!entity.validate()) {
527                                this.appendErrors(entity, flash.wizardErrors, 'entity_' + entity.getIdentifier() + '_')
528                }
529            }
530        }
531
532        flow.importer_failedcells = failedcells
533
534                return true
535        }
536
537        /**
538         * Handle the mapping page.
539         *
540         * @param Map LocalAttributeMap (the flow scope)
541         * @param Map GrailsParameterMap (the flow parameters = form data)
542         * @returns boolean true if correctly validated, otherwise false
543         */
544        boolean mappingsPage(flow, flash, params) {
545                flash.wizardErrors = [:]
546                flow.importer_invalidentities = 0
547
548                flow.importer_importeddata.each { table ->
549                        table.each { entity ->
550                                def invalidfields = 0
551
552                                // Set the fields for this entity by retrieving values from the params
553                                entity.giveFields().each { field ->
554                                        // field of type ontology and value "#invalidterm"?
555                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
556                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
557                                        ) {
558                                                invalidfields++
559                                        } else
560                                        if (field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM &&
561                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
562                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)                       
563                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
564                                        }
565                                        else
566
567                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
568                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
569                                                if (entity) removeFailedCell(flow.importer_failedcells, entity, field)                       
570                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
571                    } else
572                    if (field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST &&
573                                                params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
574                                        ) {
575                                                invalidfields++
576                                        } else
577
578                                                entity.setFieldValue(field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
579                                }
580
581                                // Determine entity class and add a parent (defined as Study in first step of wizard)
582                                switch (entity.getClass()) {
583                                        case [Subject, Sample, Event]: entity.parent = flow.importer_study
584                                }
585
586                                // Try to validate the entity now all fields have been set
587                                if (!entity.validate() || invalidfields) {
588                                        flow.importer_invalidentities++
589
590                                        // add errors to map
591                                        this.appendErrors(entity, flash.wizardErrors, "entity_" + entity.getIdentifier() + "_")
592
593                                        entity.errors.getAllErrors().each() {
594                                                log.error ".import wizard imported validation error:" + it
595                                        }
596                                } else {
597                                        //removeFailedCell(flow.importer_failedcells, entity)
598                                } // end else if
599
600                        } // end of record
601                } // end of table
602
603                return (flow.importer_invalidentities == 0) ? true : false
604        } // end of method
605
606        /**
607         * @param failedcell failed ontology cells
608         * @param entity entity to remove from the failedcells list
609         */
610        def removeFailedCell(failedcells, entity, field) {
611                // Valid entity, remove it from failedcells
612        def entityidfield = "entity_" + entity.getIdentifier() + "_" + field.name.toLowerCase()
613
614                failedcells.each { record ->
615                        def tempimportcells = []
616
617
618                        record.importcells.each { cell ->               
619                                // remove the cell from the failed cells session
620                                if (cell.entityidentifier != entityidfield) {
621                                        //record.removeFromImportcells(cell)
622                                        tempimportcells.add(cell)
623                                }
624                        }
625
626                        record.importcells = tempimportcells
627                        // } // end of importcells
628                } // end of failedcells
629        }
630
631        /**
632         * Handle the imported entities page.
633         *
634         * @param Map LocalAttributeMap (the flow scope)
635         * @param Map GrailsParameterMap (the flow parameters = form data)
636         * @returns boolean true if correctly validated, otherwise false
637         */
638        boolean importedPage(flow, params) {
639                return true
640        }
641
642        boolean saveEntities(flow, params) {
643                //def (validatedSuccesfully, updatedEntities, failedToPersist) =
644                //try {
645                ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata, authenticationService, log)
646
647                //}
648                //catch (Exception e) {
649//                log.error ".import wizard saveEntities error\n" + e.dump()
650//            }
651
652                //flow.importer_validatedsuccesfully = validatedSuccesfully
653                //flow.importer_failedtopersist = failedToPersist
654                //flow.imported_updatedentities = updatedEntities
655                //flow.importer_totalrows = flow.importer_importeddata.size
656                //flow.importer_referer = ""
657
658                return true
659        }
660
661        /**
662         * append errors of a particular object to a map
663         * @param object
664         * @param map linkedHashMap
665         * @void
666         */
667        def appendErrors(object, map) {
668                this.appendErrorMap(getHumanReadableErrors(object), map)
669        }
670
671        def appendErrors(object, map, prepend) {
672                this.appendErrorMap(getHumanReadableErrors(object), map, prepend)
673        }
674
675        /**
676         * append errors of one map to another map
677         * @param map linkedHashMap
678         * @param map linkedHashMap
679         * @void
680         */
681        def appendErrorMap(map, mapToExtend) {
682                map.each() {key, value ->
683                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
684                }
685        }
686
687        def appendErrorMap(map, mapToExtend, prepend) {
688                map.each() {key, value ->
689                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
690                }
691        }
692
693        /**
694         * transform domain class validation errors into a human readable
695         * linked hash map
696         * @param object validated domain class
697         * @return object  linkedHashMap
698         */
699        def getHumanReadableErrors(object) {
700                def errors = [:]
701                object.errors.getAllErrors().each() { error ->
702                        // error.codes.each() { code -> println code }           
703
704                        // generally speaking g.message(...) should work,
705                        // however it fails in some steps of the wizard
706                        // (add event, add assay, etc) so g is not always
707                        // availably. Using our own instance of the
708                        // validationTagLib instead so it is always
709                        // available to us
710                        errors[error.getArguments()[0]] = validationTagLib.message(error: error)
711                }
712
713                return errors
714        }
715
716}
Note: See TracBrowser for help on using the repository browser.