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

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