import grails.util.GrailsUtil import au.com.bytecode.opencsv.CSVWriter import au.com.bytecode.opencsv.CSVReader import org.apache.commons.lang.WordUtils /** * Provides some csv import/export methods. * Requires the opencsv jar to be available which is included in the grails-export plugin. */ class PersonCsvService { boolean transactional = false def authService def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib() def sessionFactory def propertyInstanceMap = org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin.PROPERTY_INSTANCE_MAP /** * Import persons creating items as required. */ def importPersons(request) { Person.withTransaction { status -> def result = [:] def kByteMultiplier = 1000 def fileMaxSize = 800 * kByteMultiplier def logFileLink = g.link(controller: "appCore", action: "appLog") {"log"} 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: "default.file.not.supplied") if (multiPartFile.getSize() > fileMaxSize) return fail(code: "default.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"]) def line = [] def lineNumber = 0 def maxNumberOfColumns = 13 def personParams = [:] def personProperties = ["loginName", "firstName", "lastName", "ROLE_Manager", "ROLE_AppUser", "ROLE_TaskManager", "ROLE_TaskUser", "ROLE_InventoryManager", "ROLE_InventoryUser", "ROLE_AssetManager", "ROLE_AssetUser", "ROLE_ProductionManager", "ROLE_ProductionUser"] def personInstance def loginNamesAndPasswords = [:] def nextLine = { line = reader.readNext() lineNumber ++ log.info "Processing line: " + lineNumber } // Get first line. nextLine() // 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: "default.file.no.header") } log.info "Header line found." // Prepare the first body line. nextLine() // Primary loop. while(line) { if(line.size() > maxNumberOfColumns) { log.error "Too many columns on line: " + lineNumber return fail(code: "person.import.failure", args: [lineNumber, logFileLink]) } // Ignore comment lines. if(line.toString().toLowerCase().contains("comment")) { log.info "Comment line found." nextLine() continue } // Ignore example lines. if(line.toString().toLowerCase().contains("example")) { log.info "Example line found." nextLine() continue } // Parse the line into the params map. personParams = [:] line.eachWithIndex { it, j -> personParams."${personProperties[j]}" = it.trim() } // Debug log.debug " Supplied params: " log.debug personParams // Ignore blank lines. if(personParams.loginName == '') { log.info "No login name found." nextLine() continue } // Login Name. personParams.loginName = personParams.loginName.toLowerCase() // First Name. personParams.firstName = WordUtils.capitalizeFully(personParams.firstName) // First Name. personParams.lastName = WordUtils.capitalizeFully(personParams.lastName) // Password. personParams.pass = personParams.pass ?: authService.randomPassword // Debug log.debug "personParams: " log.debug personParams personInstance = Person.findByLoginName(personParams.loginName) if(!personInstance) { log.info "Creating person with login name: " + personParams.loginName personInstance = new Person(loginName: personParams.loginName, firstName: personParams.firstName, lastName: personParams.lastName, pass: personParams.pass, password: authService.encodePassword(personParams.pass)) // Save. if(personInstance.hasErrors() || !personInstance.save()) { log.error "Failed to create person on line: " + lineNumber log.debug personInstance.errors return fail(code: "person.import.failure", args: [lineNumber, logFileLink]) } // Fill map with persons and passwords. loginNamesAndPasswords."${personParams.loginName}" = personParams.pass } else log.info "Person already exists with login name: " + personParams.loginName // Add Authorities. //personInstance.addToAuthorities(Authority.get(1)) if(lineNumber % 100 == 0) cleanUpGorm() if(!result.error) nextLine() } //while(line) // Success. log.info "End of file." result.loginNamesAndPasswords = g.message(code: "person.import.success") + '\n' result.loginNamesAndPasswords += "Login names and passwords: " + '\n' result.loginNamesAndPasswords += '\n' loginNamesAndPasswords.each() { result.loginNamesAndPasswords += (it.key + ' : ' + it.value + '\n') } reader.close() return result } //end withTransaction } // end importPersons() /** * Build a persons template csv file. * This template can then be populated for import. * @returns The template as a String in csv format. */ def buildPersonsTemplate() { StringWriter sw = new StringWriter() CSVWriter writer = new CSVWriter(sw) writeTemplateLines(writer) writer.close() return sw.toString() } private writeTemplateLines(writer) { writer.writeNext(templateHeaderLine1 as String[]) writer.writeNext() writer.writeNext("Comment: The header line is required.") writer.writeNext("Comment: Required columns are marked with a (*) in the header line.") writer.writeNext("Comment: Lists of items in a column must be separated by a semicolon (;), not a comma.") writer.writeNext("Comment: Role columns must be 'true' or 'false'.") writer.writeNext("Comment: Identical and existing names will be considered as the same item.") writer.writeNext("Comment: Lines containing 'comment' will be ignored.") writer.writeNext("Comment: Lines containing 'example' will be ignored.") writer.writeNext("Comment: This file must be saved as a CSV file before import.") writer.writeNext() } private getTemplateHeaderLine1() { ["Login Name*", "First Name*", "Last Name*", "Role: Business Manager*", "Role: Application User*", "Role: Task Manager*", "Role: Task User*", "Role: Inventory Manager*", "Role: Inventory User*", "Role: Asset Manager*", "Role: Asset User*", "Role: Production Manager*", "Role: Production User*"] } /** * This cleans up the hibernate session and a grails map. * For more info see: http://naleid.com/blog/2009/10/01/batch-import-performance-with-grails-and-mysql/ * The hibernate session flush is normal for hibernate. * The map is apparently used by grails for domain object validation errors. * A starting point for clean up is every 100 objects. */ def cleanUpGorm() { def session = sessionFactory.currentSession session.flush() session.clear() propertyInstanceMap.get().clear() } } // end class