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

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