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

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