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 creating items as required. * @param request The http request to run getFile against. * Get file should return a csv format file containing the asset tree as per template. */ def importAssetTree(request) { Asset.withTransaction { status -> def result = [:] def kByteMultiplier = 1000 def fileMaxSize = 500 * kByteMultiplier def multiPartFile = request.getFile('file') InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream) CSVReader reader = new CSVReader(sr) def fail = { Map m -> status.setRollbackOnly() reader.close() result.error = [ code: m.code, args: m.args ] return result } 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", args: [fileMaxSize/kByteMultiplier, "kB"]) def columnIndex = 0 def numberOfColumns = 0 def maxNumberOfColumns = 20 // Get first line. def line = reader.readNext() def lineNumber = 1 // Check for header line 1. if(line != templateHeaderLine1) { log.error "Failed to find header line 1. " log.error "Required: " + templateHeaderLine1.toString() log.error "Supplied: " + line.toString() return fail(code: "asset.tree.import.no.header") } // Get second line. line = reader.readNext() lineNumber ++ // Check for header line 2. if(line != templateHeaderLine2) { log.error "Failed to find header line 2. " log.error "Required: " + templateHeaderLine2.toString() log.error "Supplied: " + line.toString() return fail(code: "asset.tree.import.no.header") } log.info "Import checks passed, start processing asset file." // Prepare the first body line. line = reader.readNext() lineNumber ++ def siteInstance def departmentInstance def sectionInstance def assetInstance def assetSubItemInstance def parentItem while(line) { columnIndex = 0 numberOfColumns = Math.min( line.size(), maxNumberOfColumns ) if( (columnIndex+2) > numberOfColumns ) { line = reader.readNext() lineNumber ++ continue } if(line[columnIndex]) { siteInstance = Site.findByName(line[columnIndex]) if(!siteInstance) { log.info "Creating site: " + line[columnIndex] siteInstance = new Site(name: line[columnIndex], description: line[++columnIndex]) if(!siteInstance.save()) { log.error "Failed to create site on line: " + line[--columnIndex] + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else log.info "Existing site: " + siteInstance columnIndex++ if( (columnIndex+2) > numberOfColumns ) { line = reader.readNext() lineNumber ++ continue } if(line[columnIndex]) { departmentInstance = Department.findByName(line[columnIndex]) if(!departmentInstance) { departmentInstance = new Department(name: line[columnIndex], description: line[++columnIndex], site: siteInstance) if(!departmentInstance.save()) { log.error "Failed to create department on line: " + line[--columnIndex] + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } columnIndex++ if( (columnIndex+2) > numberOfColumns ) { line = reader.readNext() lineNumber ++ continue } if(line[columnIndex]) { sectionInstance = Section.findByName(line[columnIndex]) if(!sectionInstance) { sectionInstance = new Section(name: line[columnIndex], description: line[++columnIndex], site: siteInstance, department: departmentInstance) if(!sectionInstance.save()) { log.error "Failed to create section on line: " + line[--columnIndex] + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } columnIndex++ if( (columnIndex+2) > numberOfColumns ) { line = reader.readNext() lineNumber ++ continue } if(line[columnIndex]) { assetInstance = Asset.findByName(line[columnIndex]) if(!assetInstance) { assetInstance = new Asset(name: line[columnIndex], description: line[++columnIndex], section: sectionInstance) if(!assetInstance.save()) { log.error "Failed to create asset on line: " + line[--columnIndex] + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } columnIndex++ if( (columnIndex+2) > numberOfColumns ) { line = reader.readNext() lineNumber ++ continue } if(line[columnIndex]) { assetSubItemInstance = AssetSubItem.findByName(line[columnIndex]) if(!assetSubItemInstance) { assetSubItemInstance = new AssetSubItem(name: line[columnIndex], description: line[++columnIndex]) if(!assetInstance.save()) { log.error "Failed to create assetSubItem on line: " + line[--columnIndex] + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } assetInstance.addToAssetSubItems(assetSubItemInstance) columnIndex++ if( (columnIndex+2) > numberOfColumns ) { line = reader.readNext() lineNumber ++ continue } while( (columnIndex+2) < numberOfColumns ) { if(line[columnIndex]) { parentItem = assetSubItemInstance assetSubItemInstance = new AssetSubItem(name: line[columnIndex], description: line[++columnIndex], parentItem: parentItem) if(!assetInstance.save()) { log.error "Failed to create assetSubItem on line: " + line[--columnIndex] + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else break columnIndex++ } // while() } // AssetSubItem L1 } // Asset } // Section } // Department } // Site line = reader.readNext() lineNumber ++ } //while(line) // Success. reader.close() return result } //end withTransaction } // 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) writer.writeNext(templateHeaderLine1 as String[]) writer.writeNext(templateHeaderLine2 as String[]) writer.writeNext() writer.writeNext("Note: the header lines are required, start by replacing this line.") 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 private getTemplateHeaderLine1() { ["Site", "", "Department", "", "Section", "","Asset", "", "Sub Asset", "", "Functional Assembly", "", "Sub Assembly Group", "", "SubItem", ""] } private getTemplateHeaderLine2() { (["Name", "Description"])*8 } } // end class