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

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