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

Last change on this file since 1416 was 1416, checked in by t.w.abma@…, 12 years ago
  • again fixed the importer, persisting seemed to work fine, but Hibernate session was out of sync
  • 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: 1416 $
27 * $Author: t.w.abma@umcutrecht.nl $
28 * $Date: 2011-01-19 15:01:08 +0000 (wo, 19 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 "pageFour"
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                        log.error ".import wizard succesfully persisted all entities"                       
251                        def session = sessionFactory.getCurrentSession()
252                        session.clear()                       
253                        success()
254                    } else {                       
255                        log.error ".import wizard, could not save entities:\n" + e.dump()
256                        flow.page = 4
257                        error()
258                    }
259            }
260            on("error").to "error"
261            on(Exception).to "error"
262            on("success").to "finalPage"
263        }
264
265        // render errors
266        error {
267            render(view: "_error")
268            onRender {
269               
270                // Grom a development message
271                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_error.gsp".grom()
272
273                // set page to 4 so that the navigation
274                // works (it is disabled on the final page)
275                flow.page = 4
276            }
277            on("next").to "save"
278            on("previous").to "pageFour"
279        }
280
281        // last wizard page
282        finalPage {           
283            render(view: "_final_page")
284            onRender {
285                println "EEN"
286                // Grom a development message
287                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_final_page.gsp".grom()
288                println "TWEE"
289                               
290                success()
291                println "DRIE"
292            }
293            onEnd {
294                                // clean flow scope
295                                flow.clear()
296                        }
297        }
298    }
299
300    /**
301     * Return templates which belong to a certain entity type
302     *
303     * @param entity entity name string (Sample, Subject, Study et cetera)
304     * @return JSON object containing the found templates
305     */
306    def ajaxGetTemplatesByEntity = {       
307        def entityName = Blowfish.decryptBase64(           
308            URLDecoder.decode(params.entity),
309            grailsApplication.config.crypto.shared.secret
310        )
311
312        //def entityClass = grailsApplication.config.gscf.domain.importableEntities.get(params.entity).entity
313        def entityClass = entityName
314
315        // fetch all templates for a specific entity
316        def templates = Template.findAllByEntity(Class.forName(entityClass, true, this.getClass().getClassLoader()))
317
318        // render as JSON
319        render templates as JSON
320    }
321
322    /**
323     * Handle the file import page.
324     *
325     * @param Map LocalAttributeMap (the flow scope)
326     * @param Map GrailsParameterMap (the flow parameters = form data)
327     * @returns boolean true if correctly validated, otherwise false
328     */
329    boolean fileImportPage(flow, params) {
330        def importedfile = fileService.get(params['importfile'])
331        //fileService.delete(YourFile)
332
333        if (params.entity && params.template_id && importedfile.exists()) {
334            // create a workbook instance of the file
335            session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
336           
337            def selectedentities = []
338           
339            def entityName = Blowfish.decryptBase64(
340                URLDecoder.decode(params.entity),
341                grailsApplication.config.crypto.shared.secret
342            )
343           
344            def entityClass = Class.forName(entityName, true, this.getClass().getClassLoader())
345           
346            // Initialize some session variables
347            //flow.importer_workbook = wb // workbook object must be serialized for this to work           
348             
349                flow.importer_template_id = params.template_id
350                flow.importer_sheetindex = params.sheetindex.toInteger() -1 // 0 == first sheet
351                flow.importer_datamatrix_start = params.datamatrix_start.toInteger() -1 // 0 == first row
352                flow.importer_headerrow = params.headerrow.toInteger()
353
354                // Get the header from the Excel file using the arguments given in the first step of the wizard
355                flow.importer_header = ImporterService.getHeader(session.importer_workbook,
356                    flow.importer_sheetindex,
357                    flow.importer_headerrow,
358                    flow.importer_datamatrix_start,
359                    entityClass)
360
361                // Initialize 'selected entities', used to show entities above the columns
362                flow.importer_header.each {
363                    selectedentities.add([name:entityName, columnindex:it.key.toInteger()])
364                }
365               
366                flow.importer_selectedentities = selectedentities               
367
368                session.importer_datamatrix = ImporterService.getDatamatrix(
369                            session.importer_workbook, flow.importer_header,
370                            flow.importer_sheetindex,
371                            flow.importer_datamatrix_start,
372                            5)
373             
374                flow.importer_templates = Template.get(flow.importer_template_id)
375                flow.importer_allfieldtypes = "true"     
376            /*else {
377                render (template:"common/error",
378                    model:[error:"Wrong permissions: you are not allowed to write to the study you selected (${flow.importer_study})."])
379            }*/
380
381            return true
382        }
383    }
384
385    /**
386     * Handle the property mapping page.
387     *
388     * @param Map LocalAttributeMap (the flow scope)
389     * @param Map GrailsParameterMap (the flow parameters = form data)
390     * @returns boolean true if correctly validated, otherwise false
391     */
392    boolean propertiesPage (flow, params) {
393        // Find actual Template object from the chosen template name
394        def template = Template.get(flow.importer_template_id)
395
396        params.columnproperty.index.each { columnindex, property ->
397            // Create an actual class instance of the selected entity with the selected template
398            // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
399            def entityClass = Class.forName(flow.importer_header[columnindex.toInteger()].entity.getName(), true, this.getClass().getClassLoader())
400            def entityObj = entityClass.newInstance(template:template)
401
402            // Store the selected property for this column into the column map for the ImporterService
403            flow.importer_header[columnindex.toInteger()].property = property
404
405            // Look up the template field type of the target TemplateField and store it also in the map
406            flow.importer_header[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
407
408            // Is a "Don't import" property assigned to the column?
409            flow.importer_header[columnindex.toInteger()].dontimport = (property=="dontimport") ? true : false
410
411            //if it's an identifier set the mapping column true or false
412            entityObj.giveFields().each {
413                (it.preferredIdentifier && (it.name==property)) ? flow.importer_header[columnindex.toInteger()].identifier = true : false
414            }
415        }
416
417        // Import the workbook and store the table with entity records and store the failed cells
418        def (table, failedcells) = ImporterService.importData(flow.importer_template_id,
419                                                              session.importer_workbook,
420                                                              flow.importer_sheetindex,
421                                                              flow.importer_datamatrix_start,
422                                                              flow.importer_header)
423
424        flow.importer_importeddata = table       
425        flow.importer_failedcells = failedcells
426        return true
427    }
428
429    /**
430     * Handle the mapping page.
431     *
432     * @param Map LocalAttributeMap (the flow scope)
433     * @param Map GrailsParameterMap (the flow parameters = form data)
434     * @returns boolean true if correctly validated, otherwise false
435     */
436    boolean mappingsPage(flow, flash, params) {
437        flash.wizardErrors = [:]
438        flow.importer_invalidentities = 0       
439
440        flow.importer_importeddata.each { table ->
441            table.each { entity ->               
442                def invalidontologies = 0
443
444                // Set the fields for this entity by retrieving values from the params
445                entity.giveFields().each { field ->                   
446                        // field of type ontology and value "#invalidterm"?
447                        if (field.type == dbnp.studycapturing.TemplateFieldType.ONTOLOGYTERM &&
448                            params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
449                        ) {
450                            invalidontologies++
451                        } else
452                        if (field.type == dbnp.studycapturing.TemplateFieldType.ONTOLOGYTERM &&
453                            params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
454                            removeFailedCell(flow.importer_failedcells, entity)
455                            entity.setFieldValue (field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
456                        }
457                        else
458                            entity.setFieldValue (field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
459                }
460
461                // Determine entity class and add a parent (defined as Study in first step of wizard)
462                switch (entity.getClass()) {
463                    case [Subject, Sample, Event]:   entity.parent = flow.importer_study
464                }
465
466                // Try to validate the entity now all fields have been set
467                if (!entity.validate() || invalidontologies) {
468                    flow.importer_invalidentities++
469
470                    // add errors to map                   
471                    this.appendErrors(entity, flash.wizardErrors, "entity_"+entity.getIdentifier() + "_")
472                   
473                                        entity.errors.getAllErrors().each() {
474                                                log.error ".import wizard imported validation error:" + it
475                                        }
476                } else {                   
477                    removeFailedCell(flow.importer_failedcells, entity)
478                } // end else if
479
480            } // end of record
481        } // end of table
482
483        return (flow.importer_invalidentities == 0) ? true : false
484    } // end of method
485
486    /**
487     * @param failedcell failed ontology cells
488     * @param entity entity to remove from the failedcells list
489     */
490    def removeFailedCell(failedcells, entity) {       
491        // Valid entity, remove it from failedcells
492        failedcells.each { record ->
493            def tempimportcells = []
494           
495            record.importcells.each { cell ->           
496            // remove the cell from the failed cells session
497                if (cell.entityidentifier != entity.getIdentifier()) {
498                    //record.removeFromImportcells(cell)
499                    tempimportcells.add(cell)
500                }
501            }
502
503            record.importcells = tempimportcells
504           // } // end of importcells
505        } // end of failedcells
506    }
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) =
521            //try {
522                ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata)
523               
524            //}
525            //catch (Exception e) {
526//                log.error ".import wizard saveEntities error\n" + e.dump()
527//            }
528           
529            //flow.importer_validatedsuccesfully = validatedSuccesfully
530            //flow.importer_failedtopersist = failedToPersist
531            //flow.imported_updatedentities = updatedEntities
532            //flow.importer_totalrows = flow.importer_importeddata.size
533            //flow.importer_referer = ""           
534
535            return true
536    }
537
538    /**
539         * append errors of a particular object to a map
540         * @param object
541         * @param map linkedHashMap
542         * @void
543         */
544        def appendErrors(object, map) {
545                this.appendErrorMap(getHumanReadableErrors(object), map)
546    }
547
548        def appendErrors(object, map, prepend) {
549                this.appendErrorMap(getHumanReadableErrors(object), map, prepend)
550        }
551
552    /**
553         * append errors of one map to another map
554         * @param map linkedHashMap
555         * @param map linkedHashMap
556         * @void
557         */
558        def appendErrorMap(map, mapToExtend) {
559                map.each() {key, value ->
560                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]           
561                }
562        }
563
564        def appendErrorMap(map, mapToExtend, prepend) {
565                map.each() {key, value ->
566                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
567                }
568        }
569
570    /**
571         * transform domain class validation errors into a human readable
572         * linked hash map
573         * @param object validated domain class
574         * @return object  linkedHashMap
575         */
576        def getHumanReadableErrors(object) {
577                def errors = [:]
578                object.errors.getAllErrors().each() { error ->
579                        // error.codes.each() { code -> println code }           
580
581                        // generally speaking g.message(...) should work,
582                        // however it fails in some steps of the wizard
583                        // (add event, add assay, etc) so g is not always
584                        // availably. Using our own instance of the
585                        // validationTagLib instead so it is always
586                        // available to us
587                        errors[ error.getArguments()[0] ] = validationTagLib.message(error: error)
588                }
589
590                return errors
591        }
592}
Note: See TracBrowser for help on using the repository browser.