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

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

- added debug

  • 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                log.error "importerstudy="+flow.importer_study
129
130                // Trying to import a new study?
131                if (flow.importer_study)
132                    if (flow.importer_study.canWrite(AuthenticationService.getLoggedInUser())) {
133                        if (fileImportPage(flow, params)) {
134                            success()
135                        } else {
136                            error.log ".importer wizard not all fields are filled in"
137                            error()
138                        }
139                    } else
140                    {
141                        error.log ".importer wizard wrong permissions"
142                    }
143                else {
144                   if (fileImportPage(flow, params)) {
145                            success()
146                        } else {
147                            error.log ".importer wizard not all fields are filled in"
148                            error()
149                        }
150                }
151               
152                // put your bussiness logic (if applicable) in here
153            }.to "pageTwo"
154            on("toPageTwo") {
155                // put your bussiness logic (if applicable) in here
156            }.to "pageTwo"
157            on("toPageThree") {
158                // put your bussiness logic (if applicable) in here
159            }.to "pageThree"
160            on("toPageFour") {
161                // put your bussiness logic (if applicable) in here
162            }.to "pageFour"
163            on("toPageFive") {
164                // put your bussiness logic (if applicable) in here
165                flow.page = 5
166            }.to "save"
167        }
168
169        // Property to column assignment page
170        pageTwo {           
171            render(view: "_page_two")
172            onRender {
173                log.info ".import wizard properties page"
174                // Grom a development message
175                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial: pages/_page_two.gsp".grom()
176               
177                flow.page = 2
178                success()
179            }
180            on("next") {
181                if (propertiesPage(flow, params)) {                   
182                    success()
183                } else {
184                    println "properties are wrong"
185                    error()
186                }
187            }.to "pageThree"
188            on("previous").to "pageOne"
189            on("toPageOne").to "pageOne"
190            on("toPageThree").to "pageThree"
191            on("toPageFour").to "pageFour"
192            on("toPageFive") {
193                flow.page = 5
194            }.to "save"
195        }
196
197        // Mapping page
198        pageThree {           
199            render(view: "_page_three")
200            onRender {               
201                log.info ".import wizard mapping page"
202                // Grom a development message
203                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_three.gsp".grom()
204
205                flow.page = 3
206                success()
207            }           
208            on("refresh") {               
209                    success()
210            }.to "pageThree"
211            on("next") {
212                if (mappingsPage(flow, flash, params)) {
213                    success()
214                } else {
215                    log.error ".import wizard mapping error, could not validate all entities"
216                    error()
217                }
218            }.to "pageFour"
219            on("previous").to "pageTwo"
220            on("toPageOne").to "pageOne"
221            on("toPageTwo").to "pageTwo"
222            on("toPageFour").to "pageFour"
223            on("toPageFive") {
224                flow.page = 5
225            }.to "save"
226        }
227
228        // Imported data overview page
229        pageFour {
230            render(view: "_page_four")
231            onRender {
232                // Grom a development message
233                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_page_four.gsp".grom()
234
235                flow.page = 4
236                success()
237            }
238            on("next") {
239                if (importedPage(flow, params)) {
240                    success()
241                } else {
242                    log.error ".import wizard imported error, something went wrong showing the imported entities"
243                    error()
244                }
245                flow.page = 5
246            }.to "save"
247            on("previous").to "pageThree"
248            on("toPageOne").to "pageOne"
249            on("toPageTwo").to "pageTwo"
250            on("toPageThree").to "pageThree"
251            on("toPageFive") {
252                flow.page = 5
253            }.to "save"
254        }
255
256        // Save the imported data
257        save {
258            action {
259                // here you can validate and save the
260                // instances you have created in the
261                // ajax flow.
262                try {
263                    // Grom a development message
264                    if (pluginManager.getGrailsPlugin('grom')) ".persisting instances to the database...".grom()                   
265
266                    if (saveEntities(flow, params)) {                       
267                        success()
268                    } else {
269                        log.error ".import wizard imported error, something went wrong showing the imported entities"                       
270                        //throw Exception
271                    }                   
272                } catch (Exception e) {
273                    // put your error handling logic in
274                    // here                   
275                    flow.page = 4
276                    error()
277                }
278            }
279            on("error").to "error"
280            on(Exception).to "error"
281            on("success").to "finalPage"
282        }
283
284        // render errors
285        error {                       
286            render(view: "_error")
287            onRender {
288               
289                // Grom a development message
290                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_error.gsp".grom()
291
292                // set page to 4 so that the navigation
293                // works (it is disabled on the final page)
294                flow.page = 4
295            }
296            on("next").to "save"
297            on("previous").to "pageFour"
298            on("toPageOne").to "pageOne"
299            on("toPageTwo").to "pageTwo"
300            on("toPageThree").to "pageThree"
301            on("toPageFour").to "pageFour"
302            on("toPageFive").to "save"
303        }
304
305        // last wizard page
306        finalPage {
307            render(view: "_final_page")
308            onRender {
309                // Grom a development message
310                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_final_page.gsp".grom()
311                               
312                success()
313            }
314        }
315    }
316
317    /**
318     * Return templates which belong to a certain entity type
319     *
320     * @param entity entity name string (Sample, Subject, Study et cetera)
321     * @return JSON object containing the found templates
322     */
323    def ajaxGetTemplatesByEntity = {       
324        def entityName = Blowfish.decryptBase64(           
325            URLDecoder.decode(params.entity),
326            grailsApplication.config.crypto.shared.secret
327        )
328
329        //def entityClass = grailsApplication.config.gscf.domain.importableEntities.get(params.entity).entity
330        def entityClass = entityName
331
332        // fetch all templates for a specific entity
333        def templates = Template.findAllByEntity(Class.forName(entityClass, true, this.getClass().getClassLoader()))
334
335        // render as JSON
336        render templates as JSON
337    }
338
339    /**
340     * Handle the file import page.
341     *
342     * @param Map LocalAttributeMap (the flow scope)
343     * @param Map GrailsParameterMap (the flow parameters = form data)
344     * @returns boolean true if correctly validated, otherwise false
345     */
346    boolean fileImportPage(flow, params) {
347        def importedfile = fileService.get(params['importfile'])
348        //fileService.delete(YourFile)
349
350        if (params.entity && params.template_id && importedfile.exists()) {
351            // create a workbook instance of the file
352            session.importer_workbook = ImporterService.getWorkbook(new FileInputStream(importedfile))
353           
354            def selectedentities = []
355           
356            def entityName = Blowfish.decryptBase64(
357                URLDecoder.decode(params.entity),
358                grailsApplication.config.crypto.shared.secret
359            )
360           
361            def entityClass = Class.forName(entityName, true, this.getClass().getClassLoader())
362           
363            // Initialize some session variables
364            //flow.importer_workbook = wb // workbook object must be serialized for this to work           
365             
366                flow.importer_template_id = params.template_id
367                flow.importer_sheetindex = params.sheetindex.toInteger() -1 // 0 == first sheet
368                flow.importer_datamatrix_start = params.datamatrix_start.toInteger() -1 // 0 == first row
369                flow.importer_headerrow = params.headerrow.toInteger()
370
371                // Get the header from the Excel file using the arguments given in the first step of the wizard
372                flow.importer_header = ImporterService.getHeader(session.importer_workbook,
373                    flow.importer_sheetindex,
374                    flow.importer_headerrow,
375                    flow.importer_datamatrix_start,
376                    entityClass)
377
378                // Initialize 'selected entities', used to show entities above the columns
379                flow.importer_header.each {
380                    selectedentities.add([name:entityName, columnindex:it.key.toInteger()])
381                }
382               
383                flow.importer_selectedentities = selectedentities               
384
385                session.importer_datamatrix = ImporterService.getDatamatrix(
386                            session.importer_workbook, flow.importer_header,
387                            flow.importer_sheetindex,
388                            flow.importer_datamatrix_start,
389                            5)
390             
391                flow.importer_templates = Template.get(flow.importer_template_id)
392                flow.importer_allfieldtypes = "true"     
393            /*else {
394                render (template:"common/error",
395                    model:[error:"Wrong permissions: you are not allowed to write to the study you selected (${flow.importer_study})."])
396            }*/
397
398            return true
399        }
400    }
401
402    /**
403     * Handle the property mapping page.
404     *
405     * @param Map LocalAttributeMap (the flow scope)
406     * @param Map GrailsParameterMap (the flow parameters = form data)
407     * @returns boolean true if correctly validated, otherwise false
408     */
409    boolean propertiesPage (flow, params) {
410        // Find actual Template object from the chosen template name
411        def template = Template.get(flow.importer_template_id)
412
413        params.columnproperty.index.each { columnindex, property ->
414            // Create an actual class instance of the selected entity with the selected template
415            // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
416            def entityClass = Class.forName(flow.importer_header[columnindex.toInteger()].entity.getName(), true, this.getClass().getClassLoader())
417            def entityObj = entityClass.newInstance(template:template)
418
419            // Store the selected property for this column into the column map for the ImporterService
420            flow.importer_header[columnindex.toInteger()].property = property
421
422            // Look up the template field type of the target TemplateField and store it also in the map
423            flow.importer_header[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
424
425            // Is a "Don't import" property assigned to the column?
426            flow.importer_header[columnindex.toInteger()].dontimport = (property=="dontimport") ? true : false
427
428            //if it's an identifier set the mapping column true or false
429            entityObj.giveFields().each {
430                (it.preferredIdentifier && (it.name==property)) ? flow.importer_header[columnindex.toInteger()].identifier = true : false
431            }
432        }
433
434        // Import the workbook and store the table with entity records and store the failed cells
435        def (table, failedcells) = ImporterService.importData(flow.importer_template_id,
436                                                              session.importer_workbook,
437                                                              flow.importer_sheetindex,
438                                                              flow.importer_datamatrix_start,
439                                                              flow.importer_header)
440
441        flow.importer_importeddata = table
442        flow.importer_failedcells = failedcells
443        return true
444    }
445
446    /**
447     * Handle the mapping page.
448     *
449     * @param Map LocalAttributeMap (the flow scope)
450     * @param Map GrailsParameterMap (the flow parameters = form data)
451     * @returns boolean true if correctly validated, otherwise false
452     */
453    boolean mappingsPage(flow, flash, params) {
454        flash.wizardErrors = [:]
455        flow.importer_invalidentities = 0       
456
457        flow.importer_importeddata.each { table ->
458            table.each { entity ->
459                def invalidontologies = 0
460
461                // Set the fields for this entity by retrieving values from the params
462                entity.giveFields().each { field ->
463                        // field of type ontology and value "#invalidterm"?
464                        if (field.type == dbnp.studycapturing.TemplateFieldType.ONTOLOGYTERM &&
465                            params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] == "#invalidterm"
466                        ) {
467                            invalidontologies++
468                        } else
469                        if (field.type == dbnp.studycapturing.TemplateFieldType.ONTOLOGYTERM &&
470                            params["entity_" + entity.getIdentifier() + "_" + field.escapedName()] != "#invalidterm") {
471                            removeFailedCell(flow.importer_failedcells, entity)
472                            entity.setFieldValue (field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
473                        }
474                        else
475                            entity.setFieldValue (field.toString(), params["entity_" + entity.getIdentifier() + "_" + field.escapedName()])
476                }
477
478                // Determine entity class and add a parent (defined as Study in first step of wizard)
479                switch (entity.getClass()) {
480                    case [Subject, Sample, Event]:   entity.parent = flow.importer_study
481                }
482
483                log.error "mapping_importer_study="+flow.importer_study
484
485
486                // Try to validate the entity now all fields have been set
487                if (!entity.validate() || invalidontologies) {
488                    flow.importer_invalidentities++
489
490                    // add errors to map                   
491                    this.appendErrors(entity, flash.wizardErrors, "entity_"+entity.getIdentifier() + "_")
492                   
493                                        entity.errors.getAllErrors().each() {
494                                                log.error ".import wizard imported validation error:" + it
495                                        }
496                } else {                   
497                    removeFailedCell(flow.importer_failedcells, entity)
498                } // end else if
499
500            } // end of record
501        } // end of table
502
503        return (flow.importer_invalidentities == 0) ? true : false
504    } // end of method
505
506    /**
507     * @param failedcell failed ontology cells
508     * @param entity entity
509     */
510    def removeFailedCell(failedcells, entity) {
511        // Valid entity, remove it from failedcells
512        failedcells.each { record ->
513            record.importcells.each { cell ->
514            // remove the cell from the failed cells session
515                if (cell.entityidentifier == entity.getIdentifier()) {
516                    record.removeFromImportcells(cell)
517                }
518            } // end of importcells
519        } // end of failedcells
520    }
521
522    /**
523     * Handle the imported entities page.
524     *
525     * @param Map LocalAttributeMap (the flow scope)
526     * @param Map GrailsParameterMap (the flow parameters = form data)
527     * @returns boolean true if correctly validated, otherwise false
528     */
529    boolean importedPage(flow, params) {
530        return true
531    }
532
533    boolean saveEntities(flow, params) {
534            def (validatedSuccesfully, updatedEntities, failedToPersist) = ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata)
535
536            flow.importer_validatedsuccesfully = validatedSuccesfully
537            flow.importer_failedtopersist = failedToPersist
538            flow.imported_updatedentities = updatedEntities
539            flow.importer_totalrows = flow.importer_importeddata.size
540            flow.importer_referer = ""
541
542            return true
543    }
544
545    /**
546         * append errors of a particular object to a map
547         * @param object
548         * @param map linkedHashMap
549         * @void
550         */
551        def appendErrors(object, map) {
552                this.appendErrorMap(getHumanReadableErrors(object), map)
553    }
554
555        def appendErrors(object, map, prepend) {
556                this.appendErrorMap(getHumanReadableErrors(object), map, prepend)
557        }
558
559    /**
560         * append errors of one map to another map
561         * @param map linkedHashMap
562         * @param map linkedHashMap
563         * @void
564         */
565        def appendErrorMap(map, mapToExtend) {
566                map.each() {key, value ->
567                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]           
568                }
569        }
570
571        def appendErrorMap(map, mapToExtend, prepend) {
572                map.each() {key, value ->
573                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
574                }
575        }
576
577    /**
578         * transform domain class validation errors into a human readable
579         * linked hash map
580         * @param object validated domain class
581         * @return object  linkedHashMap
582         */
583        def getHumanReadableErrors(object) {
584                def errors = [:]
585                object.errors.getAllErrors().each() { error ->
586                        // error.codes.each() { code -> println code }
587            println "errors is " + error
588
589                        // generally speaking g.message(...) should work,
590                        // however it fails in some steps of the wizard
591                        // (add event, add assay, etc) so g is not always
592                        // availably. Using our own instance of the
593                        // validationTagLib instead so it is always
594                        // available to us
595                        errors[ error.getArguments()[0] ] = validationTagLib.message(error: error)
596                }
597
598                return errors
599        }
600}
Note: See TracBrowser for help on using the browser.