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

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

- uploaded file in import wizard now checked for being an Excel (formatted) file, other file (formats) will display a popup with an error message

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