root/trunk/grails-app/services/dbnp/importer/ImporterService.groovy @ 1083

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

- added logic for cleaning up 'failed cells' in the imported datamatrix

  • Property svn:keywords set to Date Author Rev
Line 
1/**
2 * Importer service
3 *
4 * The importer service handles the import of tabular, comma delimited and Excel format
5 * based files.
6 *
7 * @package     importer
8 * @author      t.w.abma@umcutrecht.nl
9 * @since       20100126
10 *
11 * Revision information:
12 * $Rev$
13 * $Author$
14 * $Date$
15 */
16
17package dbnp.importer
18import org.apache.poi.hssf.usermodel.*
19import org.apache.poi.poifs.filesystem.POIFSFileSystem
20import org.apache.poi.ss.usermodel.DataFormatter
21
22import dbnp.studycapturing.TemplateFieldType
23import dbnp.studycapturing.Template
24import dbnp.studycapturing.SamplingEvent
25import dbnp.studycapturing.Study
26import dbnp.studycapturing.Subject
27import dbnp.studycapturing.Event
28import dbnp.studycapturing.Sample
29
30import dbnp.data.Term
31
32class ImporterService {
33    def AuthenticationService
34
35    boolean transactional = true
36
37    /**
38    * @param is input stream representing the (workbook) resource
39    * @return high level representation of the workbook
40    */
41    HSSFWorkbook getWorkbook(InputStream is) {
42        POIFSFileSystem fs = new POIFSFileSystem(is)
43        HSSFWorkbook    wb = new HSSFWorkbook(fs);
44        return wb;
45    }
46
47    /**
48     * @param wb high level representation of the workbook
49     * @param sheetindex sheet to use within the workbook
50     * @return header representation as a MappingColumn hashmap
51     */
52    def getHeader(HSSFWorkbook wb, int sheetindex, int headerrow, int datamatrix_start, theEntity=null){
53
54        def sheet = wb.getSheetAt(sheetindex)   
55        def sheetrow = sheet.getRow(datamatrix_start)
56        //def header = []
57        def header = [:]
58        def df = new DataFormatter()
59        def property = new String()
60
61        //for (HSSFCell c: sheet.getRow(datamatrix_start)) {
62
63        (0..sheetrow.getLastCellNum() -1 ).each { columnindex ->
64
65            //def index =   c.getColumnIndex()
66            def datamatrix_celltype = sheet.getRow(datamatrix_start).getCell(columnindex, org.apache.poi.ss.usermodel.Row.CREATE_NULL_AS_BLANK).getCellType()
67            def datamatrix_celldata = df.formatCellValue(sheet.getRow(datamatrix_start).getCell(columnindex))
68            def datamatrix_cell     = sheet.getRow(datamatrix_start).getCell(columnindex)           
69            def headercell = sheet.getRow(headerrow-1+sheet.getFirstRowNum()).getCell(columnindex)
70            def tft = TemplateFieldType.STRING //default templatefield type
71
72            // Check for every celltype, currently redundant code, but possibly this will be
73            // a piece of custom code for every cell type like specific formatting         
74               
75            switch (datamatrix_celltype) {
76                    case HSSFCell.CELL_TYPE_STRING:
77                            //parse cell value as double
78                            def doubleBoolean = true
79                            def fieldtype = TemplateFieldType.STRING
80
81                            // is this string perhaps a double?
82                            try {
83                                formatValue(datamatrix_celldata, TemplateFieldType.DOUBLE)
84                            } catch (NumberFormatException nfe) { doubleBoolean = false }
85                            finally {
86                                if (doubleBoolean) fieldtype = TemplateFieldType.DOUBLE
87                            }
88
89                            header[columnindex] = new dbnp.importer.MappingColumn(name:df.formatCellValue(headercell),
90                                                                            templatefieldtype:fieldtype,
91                                                                            index:columnindex,
92                                                                            entity:theEntity,
93                                                                            property:property);
94
95                            break
96                    case HSSFCell.CELL_TYPE_NUMERIC:
97                            def fieldtype = TemplateFieldType.INTEGER
98                            def doubleBoolean = true
99                            def integerBoolean = true
100
101                            // is this cell really an integer?
102                            try {
103                                Integer.valueOf(datamatrix_celldata)
104                            } catch (NumberFormatException nfe) { integerBoolean = false }
105                            finally {
106                                if (integerBoolean) fieldtype = TemplateFieldType.INTEGER
107                            }
108
109                            // it's not an integer, perhaps a double?
110                            if (!integerBoolean)
111                                try {
112                                    formatValue(datamatrix_celldata, TemplateFieldType.DOUBLE)
113                                } catch (NumberFormatException nfe) { doubleBoolean = false }
114                                finally {
115                                    if (doubleBoolean) fieldtype = TemplateFieldType.DOUBLE
116                                }
117
118                            if (HSSFDateUtil.isCellDateFormatted(datamatrix_cell)) fieldtype = TemplateFieldType.DATE
119
120                            header[columnindex] = new dbnp.importer.MappingColumn(name:df.formatCellValue(headercell),
121                                                                            templatefieldtype:fieldtype,
122                                                                            index:columnindex,
123                                                                            entity:theEntity,
124                                                                            property:property);
125                            break
126                    case HSSFCell.CELL_TYPE_BLANK:
127                            header[columnindex] = new dbnp.importer.MappingColumn(name:df.formatCellValue(headercell),
128                                                                            templatefieldtype:TemplateFieldType.STRING,
129                                                                            index:columnindex,
130                                                                            entity:theEntity,
131                                                                            property:property);
132                            break
133                    default:
134                            header[columnindex] = new dbnp.importer.MappingColumn(name:df.formatCellValue(headercell),
135                                                                            templatefieldtype:TemplateFieldType.STRING,
136                                                                            index:columnindex,
137                                                                            entity:theEntity,
138                                                                            property:property);
139                            break
140            } // end of switch
141        } // end of cell loop
142        return header
143    }
144
145    /**
146     * This method is meant to return a matrix of the rows and columns
147     * used in the preview
148     *
149     * @param wb workbook object
150     * @param sheetindex sheet index used
151     * @param rows amount of rows returned
152     * @return two dimensional array (matrix) of HSSFCell objects
153     */
154
155    HSSFCell[][] getDatamatrix(HSSFWorkbook wb, header, int sheetindex, int datamatrix_start, int count) {
156        def sheet = wb.getSheetAt(sheetindex)
157        def rows  = []
158        def df = new DataFormatter()   
159
160        // walk through all rows
161        (count <= sheet.getLastRowNum()) ?
162        ((datamatrix_start+sheet.getFirstRowNum())..count).each { rowindex ->
163            def row = []
164
165            // walk through every cell
166            /*for (HSSFCell c: sheet.getRow(rowindex)) {
167                row.add(c)
168                println c.getColumnIndex() + "=" +c
169            }*/
170           
171            (0..header.size()-1).each { columnindex ->
172                def c = sheet.getRow(rowindex).getCell(columnindex, org.apache.poi.ss.usermodel.Row.CREATE_NULL_AS_BLANK)               
173                //row.add(df.formatCellValue(c))
174                row.add(c)
175                //if (c.getCellType() == c.CELL_TYPE_STRING) println "STR"+c.getStringCellValue()
176                //if (c.getCellType() == c.CELL_TYPE_NUMERIC) println "INT" +c.getNumericCellValue()
177            }
178                //row.add(df.formatCellValue(c))
179            rows.add(row)
180        } : 0
181
182        return rows
183    }
184
185    /**
186    * This method will move a file to a new location.
187    *
188    * @param file File object to move
189    * @param folderpath folder to move the file to
190    * @param filename (new) filename to give
191    * @return if file has been moved succesful, the new path and filename will be returned, otherwise an empty string will be returned
192    */
193    def moveFile(File file, String folderpath, String filename) {
194        try {
195                def rnd = ""; //System.currentTimeMillis()
196                file.transferTo(new File(folderpath, rnd+filename))
197                return folderpath + filename
198            } catch(Exception exception) {
199                log.error "File move error, ${exception}"
200                return ""
201                }
202    }
203
204    /**
205    * @return random numeric value
206    */
207    def random = {
208            return System.currentTimeMillis() + Runtime.runtime.freeMemory()
209        }
210
211    /**
212    * Method to read data from a workbook and to import data into a two dimensional
213    * array
214    *
215    * @param template_id template identifier to use fields from
216    * @param wb POI horrible spreadsheet formatted workbook object
217    * @param mcmap linked hashmap (preserved order) of MappingColumns
218    * @param sheetindex sheet to use when using multiple sheets
219    * @param rowindex first row to start with reading the actual data (NOT the header)
220    * @return two dimensional array containing records (with entities)
221    *
222    * @see dbnp.importer.MappingColumn
223    */
224    def importData(template_id, HSSFWorkbook wb, int sheetindex, int rowindex, mcmap) {
225        def sheet = wb.getSheetAt(sheetindex)
226        def table = []
227       
228        // walk through all rows and fill the table with records
229        (rowindex..sheet.getLastRowNum()).each { i ->
230            table.add(createRecord(template_id, sheet.getRow(i), mcmap))
231        }
232        return table   
233    }
234
235    /** Method to extract failed cells from the datamatrix. Failed cells are cell values
236     * which could not be stored in an entity (e.g. Humu Supiuns in an ontology field).
237     *
238     * @param datamatrix two dimensional array containing entities and possibly also failed cells
239     * @return array of failed cells in [mappingcolumn, hssfcell] format
240     * */
241    def getFailedCells(datamatrix) {
242       def failedcells = []
243       
244       datamatrix.each { record ->
245            record.each { column ->
246                column.each {                   
247                    if (it.getClass().getName().equals('java.util.LinkedHashMap$Entry')) {                       
248                        failedcells.add(it)
249                    }
250                }
251            }
252        }
253
254        return failedcells
255    }
256
257    /** Method to put failed cells back into the datamatrix. Failed cells are cell values
258     * which could not be stored in an entity (e.g. Humu Supiuns in an ontology field).
259     *
260     * @param datamatrix two dimensional array containing entities and possibly also failed cells
261     * @param failedcells map of failed cells in [mappingcolumn, hssfcell] format
262     * @param correctedcells map of corrected cells
263     **/
264    def saveCorrectedCells(datamatrix, failedcells, correctedcells) {
265        /*failedcells.each {
266            println it
267        }*/
268        def newdatamatrix = []
269
270        // Remove failed cells 'entity' / clean up datamatrix
271        datamatrix.each { record ->
272            def newrecord = []
273
274            record.each { entity ->
275              // LinkedHashMap means a "mappingcolumn:hssfcell" object is inside the record (= failed to map to entity)
276              if (!entity.getClass().getName().equals('java.util.LinkedHashMap'))
277              newrecord.add(entity)
278            }
279
280            newdatamatrix.add(newrecord)
281        }
282
283        newdatamatrix.each { record ->
284            record.each { entity ->
285                entity.giveFields().each { field ->
286                    println "das"+ field
287                }
288            }
289        }
290
291        println "-----"
292        correctedcells.each {
293            println it.dump()
294        }
295    }
296   
297    /**
298     * Method to store a matrix containing the entities in a record like structure. Every row in the table
299     * contains one or more entity objects (which contain fields with values). So actually a row represents
300     * a record with fields from one or more different entities.
301     *
302     * @param study entity Study
303     * @param datamatrix two dimensional array containing entities with values read from Excel file
304     */   
305    def saveDatamatrix(Study study, datamatrix) {
306        def validatedSuccesfully = 0
307        def entitystored = null
308        study.refresh()       
309       
310        // go through the data matrix, read every record and validate the entity and try to persist it
311        datamatrix.each { record ->
312            record.each { entity ->
313                        switch (entity.getClass()) {
314                        case Study       :  print "Persisting Study `" + entity + "`: "
315                                                entity.owner = AuthenticationService.getLoggedInUser()
316                                                if (persistEntity(entity)) validatedSuccesfully++
317                                                break
318                        case Subject     :  print "Persisting Subject `" + entity + "`: "
319                                                entity.parent = study
320                                               
321                                                // is the current entity not already in the database?
322                                                entitystored = isEntityStored(entity)
323                                               
324                                                // this entity is new, so add it to the study
325                                                if (entitystored==null) study.addToSubjects(entity)
326                                                else // existing entity, so update it
327                                                    updateEntity(entitystored, entity)
328
329                                                if (persistEntity(study)) validatedSuccesfully++
330                                                break
331                        case Event       :  print "Persisting Event `" + entity + "`: "
332                                                entity.parent = study
333                                                study.addToEvents(entity)
334                                                if (persistEntity(entity)) validatedSuccesfully++
335                                                break
336                        case Sample      :  print "Persisting Sample `" + entity +"`: "
337                                                entity.parent = study
338                                                study.addToSamples(entity)
339                                                if (persistEntity(entity)) validatedSuccesfully++
340                                                break
341                        case SamplingEvent: print "Persisting SamplingEvent `" + entity + "`: "
342                                                entity.parent = study
343                                                study.addToSamplingEvents(entity)
344                                                if (persistEntity(entity)) validatedSuccesfully++
345                                                break
346                        default          :  println "Skipping persisting of `" + entity.getclass() +"`"
347                                                break
348                        } // end switch
349            } // end record
350        } // end datamatrix
351        return validatedSuccesfully
352    }
353
354    /**
355     * Check whether an entity already exists within a study. A combination of
356     * the study where the entity will be stored and a unique field in the entity is
357     * used to check whether the instantiated entity (read from Excel) is new.
358     * If the entity is found in the database it will be returned as so and
359     * it should update the entity-values (read from Ecel) into the record in the database.
360     *
361     * @param entity entity object like a Study, Subject, Sample et cetera
362     * @return entity if found, otherwise null
363     */
364    def isEntityStored(entity) {
365            switch (entity.getClass()) {
366                        case Study          :  return Study.findByCode(entity.code)
367                                               break
368                        case Subject        :  return Subject.findByParentAndName(entity.parent, entity.name)
369                                               break
370                        case Event          :  break
371                        case Sample         :  break
372                        case SamplingEvent  :  break
373                        default             :  // unknown entity
374                                               return null
375            }
376    }
377
378    /**
379     * Find the entity and update the fields. The entity is an instance
380     * read from Excel. This method looks in the database for the entity
381     * having the same identifier. If it has found the same entity
382     * already in the database, it will update the record.
383     *
384     * @param entitystored existing record in the database to update
385     * @param entity entity read from Excel
386     */
387    def updateEntity(entitystored, entity) {
388        switch (entity.getClass()) {
389                        case Study          :  break
390                        case Subject        :  entitystored.properties = entity.properties
391                                               entitystored.save()
392                                               break
393                        case Event          :  break
394                        case Sample         :  break
395                        case SamplingEvent  :  break
396                        default             :  // unknown entity
397                                               return null
398        }
399    }
400
401    /**
402     * Method to persist entities into the database
403     * Checks whether entity already exists (based on identifier column 'name')
404     *
405     * @param entity entity object like Study, Subject, Protocol et cetera
406     *
407     */
408    boolean persistEntity(entity) {
409            println "persisting ${entity}"           
410            // if not validated
411                if (entity.validate()) {
412                        if (entity.save()) { //.merge?
413                                return true
414                        }
415                        else { // if save was unsuccesful
416                                entity.errors.allErrors.each {
417                                        println it
418                                }
419                                return false
420                        }
421                }
422            else { // if not validated
423                    entity.errors.each {
424                            println it
425                    }
426                        return false
427            }
428         }
429
430        /**
431         * This method creates a record (array) containing entities with values
432         *
433         * @param template_id template identifier
434         * @param excelrow POI based Excel row containing the cells
435         * @param mcmap map containing MappingColumn objects
436         */
437        def createRecord(template_id, HSSFRow excelrow, mcmap) {
438                def df = new DataFormatter()
439                def template = Template.get(template_id)
440                def record = []
441                def failed = [:]
442
443                // Initialize all possible entities with the chosen template
444                def study = new Study(template: template)
445                def subject = new Subject(template: template)
446                def samplingEvent = new SamplingEvent(template: template)
447                def event = new Event(template: template)
448                def sample = new Sample(template: template)
449
450                // Go through the Excel row cell by cell
451                for (HSSFCell cell: excelrow) {
452                        // get the MappingColumn information of the current cell
453                        def mc = mcmap[cell.getColumnIndex()]
454                        def value                       
455
456                        // Check if column must be imported
457                        if (mc!=null) if (!mc.dontimport) {
458                                try {
459                                        value = formatValue(df.formatCellValue(cell), mc.templatefieldtype)
460                                } catch (NumberFormatException nfe) {
461                                        value = ""
462                                }
463
464                                try {
465
466                                // which entity does the current cell (field) belong to?
467                                    switch (mc.entity) {
468                                        case Study: // does the entity already exist in the record? If not make it so.
469                                                (record.any {it.getClass() == mc.entity}) ? 0 : record.add(study)
470                                                study.setFieldValue(mc.property, value)
471                                                break
472                                        case Subject: (record.any {it.getClass() == mc.entity}) ? 0 : record.add(subject)
473                                                subject.setFieldValue(mc.property, value)
474                                                break
475                                        case SamplingEvent: (record.any {it.getClass() == mc.entity}) ? 0 : record.add(samplingEvent)
476                                                samplingEvent.setFieldValue(mc.property, value)
477                                                break
478                                        case Event: (record.any {it.getClass() == mc.entity}) ? 0 : record.add(event)
479                                                event.setFieldValue(mc.property, value)
480                                                break
481                                        case Sample: (record.any {it.getClass() == mc.entity}) ? 0 : record.add(sample)
482                                                sample.setFieldValue(mc.property, value)
483                                                break
484                                        case Object:   // don't import
485                                                break
486                                    } // end switch
487                                } catch (IllegalArgumentException iae) {
488                                    // store the mapping column and the cell in an array
489                                    failed.put(mc, cell)
490                                    //println "failed ("+mc.templatefieldtype+"`" + value + "`"
491                                }
492                        } // end
493                } // end for
494
495        // add the failed columns to the record (might also be just an empty map if nothing failed)
496        // a failed column means that using the entity.setFieldValue() threw an exception
497        //record.add(failed)
498        return record
499    }
500
501    /**
502    * Method to parse a value conform a specific type
503    * @param value string containing the value
504    * @return object corresponding to the TemplateFieldType
505    */
506    def formatValue(String value, TemplateFieldType type) throws NumberFormatException {
507            switch (type) {
508                case TemplateFieldType.STRING       :   return value.trim()
509                case TemplateFieldType.TEXT         :   return value.trim()
510                case TemplateFieldType.INTEGER      :   return (int) Double.valueOf(value)
511                case TemplateFieldType.FLOAT        :   return Float.valueOf(value.replace(",","."));
512                case TemplateFieldType.DOUBLE       :   return Double.valueOf(value.replace(",","."));
513                case TemplateFieldType.STRINGLIST   :   return value.trim()
514                case TemplateFieldType.ONTOLOGYTERM :   return value.trim()
515                case TemplateFieldType.DATE         :   return value
516                default                             :   return value
517            }
518    }
519
520}
Note: See TracBrowser for help on using the browser.