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

Last change on this file since 1422 was 1422, checked in by work@…, 10 years ago
  • fixed importer issue with the authentication service not being available in static scope
  • log was also not available in static scope
  • changed logging from println to log.info
  • Property svn:keywords set to Author Date Rev
File size: 17.8 KB
RevLine 
[1277]1package dbnp.importer
[1419]2
[1277]3import dbnp.studycapturing.Study
[1322]4import dbnp.studycapturing.Subject
[1311]5import dbnp.studycapturing.Sample
6import dbnp.studycapturing.Event
[1277]7import dbnp.studycapturing.Template
8
9import org.apache.poi.ss.usermodel.Workbook
10
11import grails.converters.JSON
12import cr.co.arquetipos.crypto.Blowfish
13
[1358]14import org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib
15
[1387]16import grails.plugins.springsecurity.Secured
17
[1416]18import org.hibernate.SessionFactory
19
[215]20/**
[1277]21 * Wizard Controller
[215]22 *
[1419]23 * @author Jeroen Wesbeek
24 * @since 20101206
[215]25 *
26 * Revision information:
27 * $Rev: 1422 $
28 * $Author: work@osx.eu $
29 * $Date: 2011-01-20 17:32:41 +0000 (do, 20 jan 2011) $
30 */
[1418]31@Secured(['IS_AUTHENTICATED_REMEMBERED'])
[1277]32class ImporterController {
[1419]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
[1421]38        def authenticationService
[1419]39        def fileService
40        def ImporterService
41        def validationTagLib = new ValidationTagLib()
[215]42
[1419]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()
[1413]50
[1419]51                // TODO --> move this logic to the application Bootstrapping as this
[1413]52                //                      does not need to run every time the importer is started
53                //
[1419]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                }
[354]62
[1419]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        }
[354]72
[1419]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()
[215]82
[1419]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'],
[1421]92                                [title: 'Assign properties'],
93                                [title: 'Check imported data'],
[1419]94                                //[title: 'Imported'],
[1421]95                                [title: 'Done']
[1419]96                        ]
97                        flow.cancel = true;
98                        flow.quickSave = true;
[1171]99
[1419]100                        success()
101                }
[215]102
[1419]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()
[989]112
[1419]113                                // let the view know we're in page 1
114                                flow.page = 1
115                                success()
116                        }
117                        on("next").to "pageOne"
118                }
[1191]119
[1419]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()
[1387]127
[1419]128                                flow.page = 1
[1421]129                                flow.studies = Study.findAllWhere(owner: authenticationService.getLoggedInUser())
[1419]130                                flow.importer_importableentities = grailsApplication.config.gscf.domain.importableEntities
[1358]131
[1419]132                                success()
133                        }
134                        on("next") {
135                                flash.wizardErrors = [:]
[1388]136
[1419]137                                // Study selected?
138                                flow.importer_study = (params.study) ? Study.get(params.study.id.toInteger()) : null
[1171]139
[1419]140                                // Trying to import data into an existing study?
141                                if (flow.importer_study)
[1421]142                                        if (flow.importer_study.canWrite(authenticationService.getLoggedInUser())) {
[1419]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)
[1171]153
[1419]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                                }
[215]165
[1419]166                                // put your bussiness logic (if applicable) in here
167                        }.to "pageTwo"
168                }
[417]169
[1419]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()
[417]177
[1419]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                }
[492]191
[1419]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()
[215]199
[1419]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)) {
[1417]250                                        //if (ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata)) {
[1422]251                                        //log.error ".import wizard succesfully persisted all entities"
[1419]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                }
[485]265
[1419]266                // render errors
267                error {
268                        render(view: "_error")
269                        onRender {
[485]270
[1419]271                                // Grom a development message
272                                if (pluginManager.getGrailsPlugin('grom')) ".rendering the partial pages/_error.gsp".grom()
[215]273
[1419]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 {
[1416]295                                // clean flow scope
296                                flow.clear()
297                        }
[1419]298                }
299        }
[215]300
[1419]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                )
[485]312
[1419]313                //def entityClass = grailsApplication.config.gscf.domain.importableEntities.get(params.entity).entity
314                def entityClass = entityName
[485]315
[1419]316                // fetch all templates for a specific entity
317                def templates = Template.findAllByEntity(Class.forName(entityClass, true, this.getClass().getClassLoader()))
[1277]318
[1419]319                // render as JSON
320                render templates as JSON
321        }
[485]322
[1419]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)
[632]333
[1419]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))
[1215]337
[1419]338                        def selectedentities = []
[1215]339
[1419]340                        def entityName = Blowfish.decryptBase64(
341                                URLDecoder.decode(params.entity),
342                                grailsApplication.config.crypto.shared.secret
343                        )
[1215]344
[1419]345                        def entityClass = Class.forName(entityName, true, this.getClass().getClassLoader())
[1083]346
[1419]347                        // Initialize some session variables
348                        //flow.importer_workbook = wb // workbook object must be serialized for this to work
[1083]349
[1419]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()
[215]354
[1419]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)
[485]361
[1419]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                        }
[245]366
[1419]367                        flow.importer_selectedentities = selectedentities
[251]368
[1419]369                        session.importer_datamatrix = ImporterService.getDatamatrix(
370                                session.importer_workbook, flow.importer_header,
371                                flow.importer_sheetindex,
372                                flow.importer_datamatrix_start,
373                                5)
[251]374
[1419]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                                                }*/
[245]381
[1419]382                        return true
383                }
384        }
[215]385
[1419]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)
[1277]396
[1419]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)
[245]402
[1419]403                        // Store the selected property for this column into the column map for the ImporterService
404                        flow.importer_header[columnindex.toInteger()].property = property
[660]405
[1419]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)
[534]408
[1419]409                        // Is a "Don't import" property assigned to the column?
410                        flow.importer_header[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false
[660]411
[1419]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                }
[1358]417
[1419]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
[1311]474                                        entity.errors.getAllErrors().each() {
475                                                log.error ".import wizard imported validation error:" + it
476                                        }
[1419]477                                } else {
478                                        removeFailedCell(flow.importer_failedcells, entity)
479                                } // end else if
[1311]480
[1419]481                        } // end of record
482                } // end of table
[669]483
[1419]484                return (flow.importer_invalidentities == 0) ? true : false
485        } // end of method
[660]486
[1419]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 = []
[1381]495
[1419]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                        }
[1367]503
[1419]504                        record.importcells = tempimportcells
505                        // } // end of importcells
506                } // end of failedcells
507        }
[449]508
[1419]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 {
[1422]523                ImporterService.saveDatamatrix(flow.importer_study, flow.importer_importeddata, authenticationService, log)
[1419]524
525                //}
526                //catch (Exception e) {
[1416]527//                log.error ".import wizard saveEntities error\n" + e.dump()
528//            }
[449]529
[1419]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 = ""
[1358]535
[1419]536                return true
537        }
538
539        /**
[1358]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)
[1419]547        }
[1358]548
549        def appendErrors(object, map, prepend) {
550                this.appendErrorMap(getHumanReadableErrors(object), map, prepend)
551        }
552
[1419]553        /**
[1358]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 ->
[1419]561                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
[1358]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
[1419]571        /**
[1358]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 ->
[1411]580                        // error.codes.each() { code -> println code }           
[1358]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
[1419]588                        errors[error.getArguments()[0]] = validationTagLib.message(error: error)
[1358]589                }
590
591                return errors
592        }
[215]593}
Note: See TracBrowser for help on using the repository browser.