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

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