source: trunk/grails-app/services/AssetCsvService.groovy @ 421

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

Add a logFileLink to the import error messages.
Small adjustment to asset import redirect.

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