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

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