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

Last change on this file since 1417 was 1417, checked in by work@…, 12 years ago
  • did some extreme programming with Tjeerd to fix issues in the importer where it either failed to finish, or failed to save template fields. Turns out it was due to the fact the method in the service was not static
  • 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: 1417 $
27 * $Author: work@osx.eu $
28 * $Date: 2011-01-20 15:10:50 +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.