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 def column = [:] def nextLine = { line = reader.readNext() lineNumber ++ log.info "Processing line: " + lineNumber } def nextColumn = { if( (columnIndex+2) > numberOfColumns ) { log.info "No more columns on line: " + lineNumber return false } if(!line[columnIndex]) { log.info "No name at " + "line: " + lineNumber + " col: " + columnIndex return false } column.name = line[columnIndex] column.description = line[++columnIndex] columnIndex++ // Success. return column } while(line) { numberOfColumns = Math.min( line.size(), maxNumberOfColumns ) columnIndex = 0 if(!nextColumn()) { nextLine() continue } siteInstance = Site.findByName(column.name) if(!siteInstance) { log.info "Creating site: " + column.name siteInstance = new Site(name: column.name, description: column.description) if(!siteInstance.save()) { log.error "Failed to create site on line: " + column.name + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else log.info "Existing site: " + siteInstance if(!nextColumn()) { nextLine() continue } departmentInstance = Department.findByName(column.name) if(!departmentInstance) { log.info "Creating department: " + column.name departmentInstance = new Department(name: column.name, description: column.description, site: siteInstance) if(!departmentInstance.save()) { log.error "Failed to create department on line: " + column.name + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else log.info "Existing department: " + departmentInstance if(!nextColumn()) { nextLine() continue } sectionInstance = Section.findByName(column.name) if(!sectionInstance) { log.info "Creating section: " + column.name sectionInstance = new Section(name: column.name, description: column.description, site: siteInstance, department: departmentInstance) if(!sectionInstance.save()) { log.error "Failed to create section on line: " + column.name + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else log.info "Existing section: " + sectionInstance if(!nextColumn()) { nextLine() continue } assetInstance = Asset.findByName(column.name) if(!assetInstance) { log.info "Creating asset: " + column.name assetInstance = new Asset(name: column.name, description: column.description, section: sectionInstance) if(!assetInstance.save()) { log.error "Failed to create asset on line: " + column.name + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else log.info "Existing asset: " + assetInstance if(!nextColumn()) { nextLine() continue } assetSubItemInstance = AssetSubItem.findByName(column.name) if(!assetSubItemInstance) { log.info "Creating asset sub item: " + column.name assetSubItemInstance = new AssetSubItem(name: column.name, description: column.description) if(!assetInstance.save()) { log.error "Failed to create assetSubItem on line: " + column.name + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else log.info "Existing asset sub item: " + assetSubItemInstance assetInstance.addToAssetSubItems(assetSubItemInstance) while( nextColumn() ) { parentItem = assetSubItemInstance assetSubItemInstance = AssetSubItem.findByName(column.name) if(!assetSubItemInstance) { log.info "Creating asset sub item: " + column.name assetSubItemInstance = new AssetSubItem(name: column.name, description: column.description, parentItem: parentItem) if(!assetSubItemInstance.save()) { log.error "Failed to create assetSubItem on line: " + column.name + "(" + lineNumber + ")" return fail(code: "asset.tree.import.failure", args: [lineNumber]) } } else log.info "Existing asset sub item: " + assetSubItemInstance } // while( nextColumn() ) nextLine() } //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