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

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