Index: trunk/grails-app/services/CsvService.groovy
===================================================================
--- trunk/grails-app/services/CsvService.groovy	(revision 271)
+++ trunk/grails-app/services/CsvService.groovy	(revision 271)
@@ -0,0 +1,181 @@
+import au.com.bytecode.opencsv.CSVWriter
+import au.com.bytecode.opencsv.CSVReader
+
+/**
+ * Provides some csv import/export methods.
+ * Requires the opencsv jar to be available which is included in the grails-export plugin.
+ */
+class CsvService {
+
+    boolean transactional = false
+
+    /**
+    * Import an asset tree for a single asset.
+    * @param file A csv file containing the asset tree for a single asset.
+    */
+    def importAssetTree(request) {
+        def result = [:]
+
+        def megaByteMultiplier = 1000 * 1000
+        def fileMaxSize = 10 * megaByteMultiplier //Mb
+
+        def fail = { Map m ->
+            //status.setRollbackOnly()
+            result.error = [ code: m.code, args: ["Import Asset Tree"] ]
+            return result
+        }
+
+        def multiPartFile = request.getFile('file')
+
+        if(!multiPartFile || multiPartFile.isEmpty())
+            return fail(code: "asset.tree.import.file.not.supplied")
+
+        if (multiPartFile.getSize() > fileMaxSize)
+            return fail(code: "asset.tree.import.file.over.max.size")
+
+        InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream)
+        CSVReader reader = new CSVReader(sr)
+
+        def nextLine = reader.readNext()
+        def lineNumber = 1
+
+        def header = ["Site", "Section", "Asset", "Sub Asset", "Functional Assembly", "Sub Assembly Group"]
+        if(nextLine != header)
+            return fail(code: "asset.tree.import.no.header")
+
+        nextLine = reader.readNext()
+        lineNumber ++
+
+        while(nextLine) {
+            lineNumber ++
+
+            println lineNumber + " : " + nextLine[0]
+
+//             if(nextLine[0]) {
+//                 if( !Site.findByName(nextLine[0].toString()) )
+//                     println nextLine[0]
+//             }
+
+            nextLine = reader.readNext()
+        } //while(nextLine)
+
+        // Success.
+        return result
+
+    } // end importAssetTree()
+
+    /**
+    * Build an asset tree template csv file.
+    * This template can then be populated for import.
+    * @returns The template as a String in csv format.
+    */
+    def buildAssetTreeTemplate() {
+
+        StringWriter sw = new StringWriter()
+        CSVWriter writer = new CSVWriter(sw)
+
+        //Header
+        def header = ["Site", "Section", "Asset", "Sub Asset", "Functional Assembly", "Sub Assembly Group"]
+        writer.writeNext(header as String[])
+
+        //Rows
+        def row
+
+        row = []
+        writer.writeNext(row as String[]) //blank row between assets.
+
+        writer.close()
+        return sw.toString()
+    }
+
+    /**
+    * Build complete asset trees for export.
+    * @param assetList The list of assets to build and export trees for.
+    * @returns The tree as a String in csv format.
+    */
+    def buildAssetTree(List assetList) {
+
+        StringWriter sw = new StringWriter()
+        CSVWriter writer = new CSVWriter(sw)
+
+        //Header
+        def header = ["Site", "Section", "Asset", "Sub Asset", "Functional Assembly", "Sub Assembly Group"]
+        writer.writeNext(header as String[])
+
+        //Rows
+        def row
+
+        def writeAssetSubItem4 = { assetSubItem ->
+            row.add(assetSubItem)
+            writer.writeNext(row as String[])
+        }
+
+        def writeAssetSubItem3 = { assetSubItem ->
+            row.add(assetSubItem)
+
+            if(assetSubItem.subItems.size() > 0) {
+                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem4 ->
+                    writeAssetSubItem4(assetSubItem4)
+                    row.remove(row.last())
+                }
+            }
+            else {
+                writer.writeNext(row as String[])
+            }
+
+        }
+
+        def writeAssetSubItem2 = { assetSubItem ->
+            row.add(assetSubItem)
+
+            if(assetSubItem.subItems.size() > 0) {
+                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem3 ->
+                    writeAssetSubItem3(assetSubItem3)
+                    row.remove(row.last())
+                }
+            }
+            else {
+                writer.writeNext(row as String[])
+            }
+
+        }
+
+        def writeAssetSubItem1 = { assetSubItem ->
+            row.add(assetSubItem)
+
+            if(assetSubItem.subItems.size() > 0) {
+                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem2 ->
+                    writeAssetSubItem2(assetSubItem2)
+                    row.remove(row.last())
+                }
+            }
+            else {
+                writer.writeNext(row as String[])
+            }
+
+        }
+
+        assetList.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { asset ->
+            row = []
+            writer.writeNext(row as String[]) //blank row between assets.
+            row.add(asset.section.site)
+            row.add(asset.section)
+            row.add(asset.name)
+
+            if(asset.assetSubItems.size() > 0) {
+                asset.assetSubItems.each() { assetSubItem1 ->
+                    writeAssetSubItem1(assetSubItem1)
+                    row.remove(row.last())
+                }
+            }
+            else {
+                writer.writeNext(row as String[])
+            }
+
+        }
+
+        writer.close()
+        return sw.toString()
+    } // end buildAssetTree
+
+} // end class
