source: trunk/grails-app/services/PersonCsvService.groovy @ 440

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

Add Person import feature.
Commit has a small amount of cross over code to the commits will follow.

File size: 9.2 KB
Line 
1import grails.util.GrailsUtil
2import au.com.bytecode.opencsv.CSVWriter
3import au.com.bytecode.opencsv.CSVReader
4import org.apache.commons.lang.WordUtils
5
6/**
7 * Provides some csv import/export methods.
8 * Requires the opencsv jar to be available which is included in the grails-export plugin.
9 */
10class PersonCsvService {
11
12    boolean transactional = false
13
14    def authService
15
16    def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib()
17
18    def sessionFactory
19    def propertyInstanceMap = org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin.PROPERTY_INSTANCE_MAP
20
21    /**
22    * Import persons creating items as required.
23    */
24    def importPersons(request) {
25        Person.withTransaction { status ->
26            def result = [:]
27
28            def kByteMultiplier = 1000
29            def fileMaxSize = 800 * kByteMultiplier
30            def logFileLink = g.link(controller: "appCore", action: "appLog") {"log"}
31
32            def multiPartFile = request.getFile('file')
33
34            InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream)
35            CSVReader reader = new CSVReader(sr)
36
37            def fail = { Map m ->
38                status.setRollbackOnly()
39                reader.close()
40                result.error = [ code: m.code, args: m.args ]
41                return result
42            }
43
44            if(!multiPartFile || multiPartFile.isEmpty())
45                return fail(code: "default.file.not.supplied")
46
47            if (multiPartFile.getSize() > fileMaxSize)
48                return fail(code: "default.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"])
49
50            def line = []
51            def lineNumber = 0
52            def maxNumberOfColumns = 13
53            def personParams = [:]
54            def personProperties = ["loginName", "firstName", "lastName",
55                                                    "ROLE_Manager", "ROLE_AppUser",
56                                                    "ROLE_TaskManager", "ROLE_TaskUser",
57                                                    "ROLE_InventoryManager", "ROLE_InventoryUser",
58                                                    "ROLE_AssetManager", "ROLE_AssetUser",
59                                                    "ROLE_ProductionManager", "ROLE_ProductionUser"]
60
61            def personInstance
62            def loginNamesAndPasswords = [:]
63
64            def nextLine = {
65                    line = reader.readNext()
66                    lineNumber ++
67                    log.info "Processing line: " + lineNumber
68            }
69
70            // Get first line.
71            nextLine()
72
73            // Check for header line 1.
74            if(line != templateHeaderLine1) {
75                log.error "Failed to find header line 1. "
76                log.error "Required: " + templateHeaderLine1.toString()
77                log.error "Supplied: " + line.toString()
78                return fail(code: "default.file.no.header")
79            }
80
81            log.info "Header line found."
82
83            // Prepare the first body line.
84            nextLine()
85
86            // Primary loop.
87            while(line) {
88
89                if(line.size() > maxNumberOfColumns) {
90                    log.error "Too many columns on line: " + lineNumber
91                    return fail(code: "person.import.failure", args: [lineNumber, logFileLink])
92                }
93
94                // Ignore comment lines.
95                if(line.toString().toLowerCase().contains("comment")) {
96                    log.info "Comment line found."
97                    nextLine()
98                    continue
99                }
100
101                // Ignore example lines.
102                if(line.toString().toLowerCase().contains("example")) {
103                    log.info "Example line found."
104                    nextLine()
105                    continue
106                }
107
108                // Parse the line into the params map.
109                personParams = [:]
110                line.eachWithIndex { it, j ->
111                    personParams."${personProperties[j]}" = it.trim()
112                }
113
114                // Debug
115                log.debug " Supplied params: "
116                log.debug personParams
117
118                // Ignore blank lines.
119                if(personParams.loginName == '') {
120                    log.info "No login name found."
121                    nextLine()
122                    continue
123                }
124
125                // Login Name.
126                personParams.loginName = personParams.loginName.toLowerCase()
127
128                // First Name.
129                personParams.firstName = WordUtils.capitalizeFully(personParams.firstName)
130
131                // First Name.
132                personParams.lastName = WordUtils.capitalizeFully(personParams.lastName)
133
134                // Password.
135                personParams.pass = personParams.pass ?: authService.randomPassword
136
137                // Debug
138                log.debug "personParams: "
139                log.debug personParams
140
141                personInstance = Person.findByLoginName(personParams.loginName)
142
143                if(!personInstance) {
144                    log.info "Creating person with login name: " + personParams.loginName
145                    personInstance = new Person(loginName: personParams.loginName,
146                                                                                firstName: personParams.firstName,
147                                                                                lastName: personParams.lastName,
148                                                                                pass: personParams.pass,
149                                                                                password: authService.encodePassword(personParams.pass))
150
151                    // Save.
152                    if(personInstance.hasErrors() || !personInstance.save()) {
153                        log.error "Failed to create person on line: " + lineNumber
154                        log.debug personInstance.errors
155                        return fail(code: "person.import.failure", args: [lineNumber, logFileLink])
156                    }
157
158                    // Fill map with persons and passwords.
159                    loginNamesAndPasswords."${personParams.loginName}" = personParams.pass
160                }
161                else
162                    log.info "Person already exists with login name: " + personParams.loginName
163
164                // Add Authorities.
165                //personInstance.addToAuthorities(Authority.get(1))
166
167                if(lineNumber % 100 == 0)
168                    cleanUpGorm()
169
170                if(!result.error) nextLine()
171            } //while(line)
172
173            // Success.
174            log.info "End of file."
175            result.loginNamesAndPasswords = g.message(code: "person.import.success") + '\n'
176            result.loginNamesAndPasswords += "Login names and passwords: " + '\n'
177            result.loginNamesAndPasswords += '\n'
178            loginNamesAndPasswords.each() { result.loginNamesAndPasswords += (it.key + ' : ' + it.value + '\n') }
179            reader.close()
180            return result
181
182         } //end withTransaction
183    } // end importPersons()
184
185    /**
186    * Build a persons template csv file.
187    * This template can then be populated for import.
188    * @returns The template as a String in csv format.
189    */
190    def buildPersonsTemplate() {
191
192        StringWriter sw = new StringWriter()
193        CSVWriter writer = new CSVWriter(sw)
194
195        writeTemplateLines(writer)
196
197        writer.close()
198        return sw.toString()
199    }
200
201    private writeTemplateLines(writer) {
202        writer.writeNext(templateHeaderLine1 as String[])
203        writer.writeNext()
204        writer.writeNext("Comment: The header line is required.")
205        writer.writeNext("Comment: Required columns are marked with a (*) in the header line.")
206        writer.writeNext("Comment: Lists of items in a column must be separated by a semicolon (;), not a comma.")
207        writer.writeNext("Comment: Role columns must be 'true' or 'false'.")
208        writer.writeNext("Comment: Identical and existing names will be considered as the same item.")
209        writer.writeNext("Comment: Lines containing 'comment' will be ignored.")
210        writer.writeNext("Comment: Lines containing 'example' will be ignored.")
211        writer.writeNext("Comment: This file must be saved as a CSV file before import.")
212        writer.writeNext()
213    }
214
215    private getTemplateHeaderLine1() {
216            ["Login Name*", "First Name*", "Last Name*",
217            "Role: Business Manager*", "Role: Application User*",
218            "Role: Task Manager*", "Role: Task User*",
219            "Role: Inventory Manager*", "Role: Inventory User*",
220            "Role: Asset Manager*", "Role: Asset User*",
221            "Role: Production Manager*", "Role: Production User*"]
222    }
223
224    /**
225    * This cleans up the hibernate session and a grails map.
226    * For more info see: http://naleid.com/blog/2009/10/01/batch-import-performance-with-grails-and-mysql/
227    * The hibernate session flush is normal for hibernate.
228    * The map is apparently used by grails for domain object validation errors.
229    * A starting point for clean up is every 100 objects.
230    */
231    def cleanUpGorm() {
232        def session = sessionFactory.currentSession
233        session.flush()
234        session.clear()
235        propertyInstanceMap.get().clear()
236    }
237
238} // end class
Note: See TracBrowser for help on using the repository browser.