root/trunk/grails-app/controllers/dbnp/importer/ImporterController.groovy @ 1372

Revision 1372, 22.6 KB (checked in by t.w.abma@…, 3 years ago)

- moved debug statement (locally no errors, but on CI gives NPE)

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