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

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