source: trunk/grails-app/services/CsvService.groovy @ 315

Last change on this file since 315 was 315, checked in by gav, 10 years ago

Improvements to CsvService.
More comments to code and asset tree template.
Add description column to asset tree export and make import more robust with trimming and capitalising.

File size: 17.3 KB
Line 
1import au.com.bytecode.opencsv.CSVWriter
2import au.com.bytecode.opencsv.CSVReader
3import org.apache.commons.lang.WordUtils
4
5/**
6 * Provides some csv import/export methods.
7 * Requires the opencsv jar to be available which is included in the grails-export plugin.
8 */
9class CsvService {
10
11    boolean transactional = false
12
13    /**
14    * Import an asset tree creating items as required.
15    * @param request The http request to run getFile against.
16    * Get file should return a csv format file containing the asset tree as per template.
17    */
18    def importAssetTree(request) {
19        Asset.withTransaction { status ->
20            def result = [:]
21
22            def kByteMultiplier = 1000
23            def fileMaxSize = 500 * kByteMultiplier
24
25            def multiPartFile = request.getFile('file')
26
27            InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream)
28            CSVReader reader = new CSVReader(sr)
29
30            def fail = { Map m ->
31                status.setRollbackOnly()
32                reader.close()
33                result.error = [ code: m.code, args: m.args ]
34                return result
35            }
36
37            if(!multiPartFile || multiPartFile.isEmpty())
38                return fail(code: "asset.tree.import.file.not.supplied")
39
40            if (multiPartFile.getSize() > fileMaxSize)
41                return fail(code: "asset.tree.import.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"])
42
43            def columnIndex = 0
44            def numberOfColumns = 0
45            def maxNumberOfColumns = 20
46
47            // Get first line.
48            def line = reader.readNext()
49            def lineNumber = 1
50
51            // Check for header line 1.
52            if(line != templateHeaderLine1) {
53                log.error "Failed to find header line 1. "
54                log.error "Required: " + templateHeaderLine1.toString()
55                log.error "Supplied: " + line.toString()
56                return fail(code: "asset.tree.import.no.header")
57            }
58
59            // Get second line.
60            line = reader.readNext()
61            lineNumber ++
62
63            // Check for header line 2.
64            if(line != templateHeaderLine2) {
65                log.error "Failed to find header line 2. "
66                log.error "Required: " + templateHeaderLine2.toString()
67                log.error "Supplied: " + line.toString()
68                return fail(code: "asset.tree.import.no.header")
69            }
70
71            log.info "Import checks passed, start processing asset file."
72
73            // Prepare the first body line.
74            line = reader.readNext()
75            lineNumber ++
76
77            def siteInstance
78            def departmentInstance
79            def sectionInstance
80            def assetInstance
81            def assetSubItemInstance
82            def parentItem
83
84            def column = [:]
85
86            def nextLine = {
87                    line = reader.readNext()
88                    lineNumber ++
89                    log.info "Processing line: " + lineNumber
90            }
91
92            def nextColumn = {
93                if( (columnIndex+2) > numberOfColumns ) {
94                    log.info "No more columns on line: " + lineNumber
95                    return false
96                }
97                if(!line[columnIndex]) {
98                    log.info "No name at " + "line: " + lineNumber + " col: " + columnIndex
99                    return false
100                }
101                // Capitalise and assign the name and description columns.
102                use(WordUtils) {
103                column.name = line[columnIndex].trim().capitalize()
104                column.description = line[++columnIndex].trim().capitalize()
105                }
106                columnIndex++
107                // Success.
108                return column
109            }
110
111            // Primary loop.
112            while(line) {
113                numberOfColumns = Math.min( line.size(), maxNumberOfColumns )
114                columnIndex = 0
115
116                if(!nextColumn()) {
117                    nextLine()
118                    continue
119                }
120
121                // Ignore comment lines.
122                if(line.toString().toLowerCase().contains("comment")) {
123                    log.info "Comment line found."
124                    nextLine()
125                    continue
126                }
127
128                // Ignore example lines.
129                if(line.toString().toLowerCase().contains("example")) {
130                    log.info "Example line found."
131                    nextLine()
132                    continue
133                }
134
135                // Site.
136                siteInstance = Site.findByName(column.name)
137                if(!siteInstance) {
138                    log.info "Creating site: " + column.name
139                    siteInstance = new Site(name: column.name,
140                                                                description: column.description)
141                    if(!siteInstance.save()) {
142                        log.error "Failed to create site on line: " + column.name + "(" + lineNumber + ")"
143                        return fail(code: "asset.tree.import.failure", args: [lineNumber])
144                    }
145                }
146                else log.info "Existing site: " + siteInstance
147
148                if(!nextColumn()) {
149                    nextLine()
150                    continue
151                }
152
153                // Department.
154                departmentInstance = Department.findByName(column.name)
155                if(!departmentInstance) {
156                    log.info "Creating department: " + column.name
157                    departmentInstance = new Department(name: column.name,
158                                                                                            description: column.description,
159                                                                                            site: siteInstance)
160                    if(!departmentInstance.save()) {
161                        log.error "Failed to create department on line: " + column.name + "(" + lineNumber + ")"
162                        return fail(code: "asset.tree.import.failure", args: [lineNumber])
163                    }
164                }
165                else log.info "Existing department: " + departmentInstance
166
167                if(!nextColumn()) {
168                    nextLine()
169                    continue
170                }
171
172                // Section.
173                sectionInstance = Section.findByName(column.name)
174                if(!sectionInstance) {
175                    log.info "Creating section: " + column.name
176                    sectionInstance =  new Section(name: column.name,
177                                                                            description: column.description,
178                                                                            site: siteInstance,
179                                                                            department: departmentInstance)
180                    if(!sectionInstance.save()) {
181                        log.error "Failed to create section on line: " + column.name + "(" + lineNumber + ")"
182                        return fail(code: "asset.tree.import.failure", args: [lineNumber])
183                    }
184                }
185                else log.info "Existing section: " + sectionInstance
186
187                if(!nextColumn()) {
188                    nextLine()
189                    continue
190                }
191
192                // Asset.
193                assetInstance = Asset.findByName(column.name)
194                if(!assetInstance) {
195                    log.info "Creating asset: " + column.name
196                    assetInstance = new Asset(name: column.name,
197                                                                    description: column.description,
198                                                                    section: sectionInstance)
199                    if(!assetInstance.save()) {
200                        log.error "Failed to create asset on line: " + column.name + "(" + lineNumber + ")"
201                        return fail(code: "asset.tree.import.failure", args: [lineNumber])
202                    }
203                }
204                else log.info "Existing asset: " + assetInstance
205
206                if(!nextColumn()) {
207                    nextLine()
208                    continue
209                }
210
211                // AssetSubItem Level 1.
212                assetSubItemInstance = AssetSubItem.findByName(column.name)
213                if(!assetSubItemInstance) {
214                    log.info "Creating asset sub item: " + column.name
215                    assetSubItemInstance = new AssetSubItem(name: column.name,
216                                                                                                description: column.description)
217                    if(!assetInstance.save()) {
218                        log.error "Failed to create assetSubItem on line: " + column.name + "(" + lineNumber + ")"
219                        return fail(code: "asset.tree.import.failure", args: [lineNumber])
220                    }
221                }
222                else log.info "Existing asset sub item: " + assetSubItemInstance
223
224                assetInstance.addToAssetSubItems(assetSubItemInstance)
225
226                // Remaining AssetSubItems.
227                while( nextColumn() ) {
228
229                    parentItem = assetSubItemInstance
230                    assetSubItemInstance = AssetSubItem.findByName(column.name)
231                    if(!assetSubItemInstance) {
232                        log.info "Creating asset sub item: " + column.name
233                        assetSubItemInstance = new AssetSubItem(name: column.name,
234                                                                                                    description: column.description,
235                                                                                                    parentItem: parentItem)
236                        if(!assetSubItemInstance.save()) {
237                            log.error "Failed to create assetSubItem on line: " + column.name + "(" + lineNumber + ")"
238                            return fail(code: "asset.tree.import.failure", args: [lineNumber])
239                        }
240                    }
241                    else log.info "Existing asset sub item: " + assetSubItemInstance
242
243                } // while( nextColumn() )
244
245                nextLine()
246            } //while(line)
247
248            // Success.
249            log.info "End of file."
250            reader.close()
251            return result
252
253        } //end withTransaction
254    } // end importAssetTree()
255
256    /**
257    * Build an asset tree template csv file.
258    * This template can then be populated for import.
259    * @returns The template as a String in csv format.
260    */
261    def buildAssetTreeTemplate() {
262
263        StringWriter sw = new StringWriter()
264        CSVWriter writer = new CSVWriter(sw)
265
266        writeTemplateLines(writer)
267
268        writer.close()
269        return sw.toString()
270    }
271
272    private writeTemplateLines(writer) {
273        writer.writeNext(templateHeaderLine1 as String[])
274        writer.writeNext(templateHeaderLine2 as String[])
275        writer.writeNext()
276        writer.writeNext(["Example: Site1", "", "Department 1", "", "Section 1", "", "Asset 1", ""] as String[])
277        writer.writeNext()
278        writer.writeNext(["Example: Site1", "", "Department 1", "", "Section 1", "", "Asset 2", ""] as String[])
279        writer.writeNext()
280        writer.writeNext("Comment: The first two header lines are required.")
281        writer.writeNext("Comment: Leave a blank line between assets.")
282        writer.writeNext("Comment: Names are required, descriptions are optional.")
283        writer.writeNext("Comment: Identical and existing names will be considered as the same item.")
284        writer.writeNext("Comment: An asset may share the first level of asset sub items with other assets.")
285        writer.writeNext("Comment: Lower levels of asset sub items are only parented once but may have many children.")
286        writer.writeNext("Comment: Lines containing 'comment' will be ignored.")
287        writer.writeNext("Comment: Lines containing 'example' will be ignored.")
288        writer.writeNext("Comment: This file must be saved as a CSV file before import.")
289        writer.writeNext()
290    }
291
292    /**
293    * Build an asset tree test file.
294    * This test file can be imported to test the import and template methods.
295    * @returns The test file as a String in csv format.
296    */
297    def buildAssetTreeTest() {
298
299        StringWriter sw = new StringWriter()
300        CSVWriter writer = new CSVWriter(sw)
301
302        writeTemplateLines(writer)
303
304        writer.writeNext(["Lake Press", "Lake Press Site",
305                                        "Print Department", "The printing department",
306                                        "Press Section", "Contains all printing units",
307                                        "Print Unit 1", "Printing Unit Number 1",
308                                        "Print Unit", "Print Unit (Common to all units)",
309                                        "Print Couple", "Includes  blanket cylinder",
310                                        "Motor", "Main Drive Motor",
311                                        "NDS Bearing", "Non Drive Side Main Bearing"
312                                        ] as String[])
313
314        writer.writeNext(["Lake Press", "Lake Press Site",
315                                        "Print Department", "The printing department",
316                                        "Press Section", "Contains all printing units",
317                                        "Print Unit 1", "Printing Unit Number 1",
318                                        "Print Unit", "Print Unit (Common to all units)",
319                                        "Print Couple", "Includes  blanket cylinder",
320                                        "Motor", "Main Drive Motor",
321                                        "DS Bearing", "Drive Side Main Bearing"
322                                        ] as String[])
323
324        writer.close()
325        return sw.toString()
326    }
327
328    /**
329    * Build complete asset trees for export.
330    * @param assetList The list of assets to build and export trees for.
331    * @returns The tree as a String in csv format.
332    */
333    def buildAssetTree(List assetList) {
334
335        StringWriter sw = new StringWriter()
336        CSVWriter writer = new CSVWriter(sw)
337
338        writeTemplateLines(writer)
339
340        //Rows
341        def row
342
343        def writeAssetSubItem4 = { assetSubItem ->
344            row.add(assetSubItem.name)
345            row.add(assetSubItem.description)
346            writer.writeNext(row as String[])
347        }
348
349        def writeAssetSubItem3 = { assetSubItem ->
350            row.add(assetSubItem.name)
351            row.add(assetSubItem.description)
352
353            if(assetSubItem.subItems.size() > 0) {
354                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem4 ->
355                    writeAssetSubItem4(assetSubItem4)
356                    row.remove(row.last())
357                    row.remove(row.last())
358                }
359            }
360            else {
361                writer.writeNext(row as String[])
362            }
363
364        }
365
366        def writeAssetSubItem2 = { assetSubItem ->
367            row.add(assetSubItem.name)
368            row.add(assetSubItem.description)
369
370            if(assetSubItem.subItems.size() > 0) {
371                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem3 ->
372                    writeAssetSubItem3(assetSubItem3)
373                    row.remove(row.last())
374                    row.remove(row.last())
375                }
376            }
377            else {
378                writer.writeNext(row as String[])
379            }
380
381        }
382
383        def writeAssetSubItem1 = { assetSubItem ->
384            row.add(assetSubItem.name)
385            row.add(assetSubItem.description)
386
387            if(assetSubItem.subItems.size() > 0) {
388                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem2 ->
389                    writeAssetSubItem2(assetSubItem2)
390                    row.remove(row.last())
391                    row.remove(row.last())
392                }
393            }
394            else {
395                writer.writeNext(row as String[])
396            }
397
398        }
399
400        assetList.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { asset ->
401            row = []
402            writer.writeNext(row as String[]) //blank row between assets.
403            row.add(asset.section.site.name)
404            row.add(asset.section.site.description)
405            row.add(asset.section.department.name)
406            row.add(asset.section.department.description)
407            row.add(asset.section.name)
408            row.add(asset.section.description)
409            row.add(asset.name)
410            row.add(asset.description)
411
412            if(asset.assetSubItems.size() > 0) {
413                asset.assetSubItems.each() { assetSubItem1 ->
414                    writeAssetSubItem1(assetSubItem1)
415                    row.remove(row.last())
416                    row.remove(row.last())
417                }
418            }
419            else {
420                writer.writeNext(row as String[])
421            }
422
423        }
424
425        writer.close()
426        return sw.toString()
427    } // end buildAssetTree
428
429    private getTemplateHeaderLine1() {
430            ["Site", "", "Department", "", "Section", "","Asset", "", "Sub Asset", "", "Functional Assembly", "", "Sub Assembly Group", "", "SubItem", ""]
431    }
432
433    private getTemplateHeaderLine2() {
434            (["Name", "Description"])*8
435    }
436
437} // end class
Note: See TracBrowser for help on using the repository browser.