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

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