source: trunk/grails-app/domain/dbnp/studycapturing/TemplateEntity.groovy @ 409

Last change on this file since 409 was 409, checked in by keesvb, 10 years ago

OK, that refactoring was too enthusiastic, switching back getField() to pure TemplateField? collections fixes the strange TemplateFieldListItem?.id bugs

  • Property svn:keywords set to Date Author Rev
File size: 12.9 KB
RevLine 
[212]1package dbnp.studycapturing
2
3import dbnp.data.Term
[332]4import org.springframework.validation.FieldError
[212]5
[238]6/**
7 * TemplateEntity Domain Class
8 *
9 * Revision information:
10 * $Rev: 409 $
11 * $Author: keesvb $
12 * $Date: 2010-05-11 14:47:22 +0000 (di, 11 mei 2010) $
13 */
[313]14abstract class TemplateEntity implements Serializable {
[224]15        Template template
[236]16        Map templateStringFields = [:]
17        Map templateTextFields = [:]
18        Map templateStringListFields = [:]
19        Map templateIntegerFields = [:]
20        Map templateFloatFields = [:]
21        Map templateDoubleFields = [:]
22        Map templateDateFields = [:]
23        Map templateTermFields = [:]
[212]24
25        static hasMany = [
[224]26                templateStringFields: String,
[236]27                templateTextFields: String,
[224]28                templateStringListFields: TemplateFieldListItem,
[212]29                templateIntegerFields: int,
30                templateFloatFields: float,
31                templateDoubleFields: double,
32                templateDateFields: Date,
[384]33                templateTermFields: Term,
34                systemFields: TemplateField
[212]35        ]
36
[236]37        static mapping = {
38                tablePerHierarchy false
39
40                templateTextFields type: 'text'
[332]41        }       
42
43        /**
[335]44         * Constraints
[332]45         *
46         * All template fields have their own custom validator. Note that there
[335]47         * currently is a lot of code repetition. Ideally we don't want this, but
48         * unfortunately due to scope issues we cannot re-use the code. So make
49         * sure to replicate any changes to all pieces of logic! Only commented
50         * the first occurrence of the logic, please refer to the templateStringFields
51         * validator if you require information about the validation logic...
[332]52         */
53        static constraints = {
54                template(nullable: true, blank: true)
55                templateStringFields(validator: { fields, obj, errors ->
[335]56                        // note that we only use 'fields' and 'errors', 'obj' is
57                        // merely here because it's the way the closure is called
58                        // by the validator...
59
60                        // define a boolean
[332]61                        def error = false
62
63                        // iterate through fields
64                        fields.each { key, value ->
[335]65                                // check if the value is of proper type
66                                if ( value && value.class != String ) {
67                                        // it's of some other type
[332]68                                        try {
[335]69                                                // try to cast it to the proper type
[332]70                                                fields[key] = (value as String)
71                                        } catch (Exception e) {
[335]72                                                // could not typecast properly, value is of improper type
73                                                // add error message
[332]74                                                error = true
75                                                errors.rejectValue(
76                                                        'templateStringFields',
77                                                        'templateEntity.typeMismatch.string',
78                                                        [key, value.class] as Object[],
79                                                        'Property {0} must be of type String and is currently of type {1}'
80                                                )
81                                        }
82                                }
83                        }
[335]84
85                        // got an error, or not?
[332]86                        return (!error)
87                })
88                templateTextFields(validator: { fields, obj, errors ->
89                        def error = false
90                        fields.each { key, value ->
[335]91                                if ( value && value.class != String ) {
[332]92                                        try {
93                                                fields[key] = (value as String)
94                                        } catch (Exception e) {
95                                                error = true
96                                                errors.rejectValue(
97                                                        'templateTextFields',
98                                                        'templateEntity.typeMismatch.string',
99                                                        [key, value.class] as Object[],
100                                                        'Property {0} must be of type String and is currently of type {1}'
101                                                )
102                                        }
103                                }
104                        }
105                        return (!error)
106                })
107                templateStringListFields(validator: { fields, obj, errors ->
108                        def error = false
109                        fields.each { key, value ->
[335]110                                if ( value && value.class != TemplateFieldListItem ) {
[332]111                                        try {
112                                                fields[key] = (value as TemplateFieldListItem)
113                                        } catch (Exception e) {
114                                                error = true
115                                                errors.rejectValue(
116                                                        'templateIntegerFields',
117                                                        'templateEntity.typeMismatch.templateFieldListItem',
118                                                        [key, value.class] as Object[],
119                                                        'Property {0} must be of type TemplateFieldListItem and is currently of type {1}'
120                                                )
121                                        }
122                                }
123                        }
124                        return (!error)
125                })
126                templateIntegerFields(validator: { fields, obj, errors ->
127                        def error = false
128                        fields.each { key, value ->
[335]129                                if (value && value.class != Integer ) {
[332]130                                        try {
131                                                fields[key] = (value as Integer)
132                                        } catch (Exception e) {
133                                                error = true
134                                                errors.rejectValue(
135                                                        'templateIntegerFields',
136                                                        'templateEntity.typeMismatch.integer',
137                                                        [key, value.class] as Object[],
138                                                        'Property {0} must be of type Integer and is currently of type {1}'
139                                                )
140                                        }
141                                }
142                        }
143                        return (!error)
144                })
145                templateFloatFields(validator: { fields, obj, errors ->
146                        def error = false
147                        fields.each { key, value ->
[335]148                                if ( value && value.class != Float ) {
[332]149                                        try {
150                                                fields[key] = (value as Float)
151                                        } catch (Exception e) {
152                                                error = true
153                                                errors.rejectValue(
154                                                        'templateFloatFields',
155                                                        'templateEntity.typeMismatch.float',
156                                                        [key, value.class] as Object[],
157                                                        'Property {0} must be of type Float and is currently of type {1}'
158                                                )
159                                        }
160                                }
161                        }
162                        return (!error)
163                })
164                templateDoubleFields(validator: { fields, obj, errors ->
165                        def error = false
166                        fields.each { key, value ->
[335]167                                if ( value && value.class != Double ) {
[332]168                                        try {
169                                                fields[key] = (value as Double)
170                                        } catch (Exception e) {
171                                                error = true
172                                                errors.rejectValue(
173                                                        'templateDoubleFields',
174                                                        'templateEntity.typeMismatch.double',
175                                                        [key, value.class] as Object[],
176                                                        'Property {0} must be of type Double and is currently of type {1}'
177                                                )
178                                        }
179                                }
180                        }
181                        return (!error)
182                })
183                templateDateFields(validator: { fields, obj, errors ->
184                        def error = false
185                        fields.each { key, value ->
[335]186                                if ( value && value.class != Date ) {
[332]187                                        try {
188                                                fields[key] = (value as Date)
189                                        } catch (Exception e) {
190                                                error = true
191                                                errors.rejectValue(
192                                                        'templateDateFields',
193                                                        'templateEntity.typeMismatch.date',
194                                                        [key, value.class] as Object[],
195                                                        'Property {0} must be of type Date and is currently of type {1}'
196                                                )
197                                        }
198                                }
199                        }
200                        return (!error)
201                })
202                templateTermFields(validator: { fields, obj, errors ->
203                        def error = false
204                        fields.each { key, value ->
[335]205                                if ( value && value.class != Term ) {
[332]206                                        try {
207                                                fields[key] = (value as Term)
208                                        } catch (Exception e) {
209                                                error = true
210                                                errors.rejectValue(
211                                                        'templateTermFields',
212                                                        'templateEntity.typeMismatch.term',
213                                                        [key, value.class] as Object[],
214                                                        'Property {0} must be of type Term and is currently of type {1}'
215                                                )
216                                        }
217                                }
218                        }
219                        return (!error)
220                })
[236]221        }
222
[335]223        /**
224         * Get the proper templateFields Map for a specific field type
225         * @param TemplateFieldType
226         * @return pointer
227         * @visibility private
228         * @throws NoSuchFieldException
229         */
[236]230        private Map getStore(TemplateFieldType fieldType) {
[212]231                switch(fieldType) {
[236]232                        case TemplateFieldType.STRING:
233                                return templateStringFields
234                        case TemplateFieldType.TEXT:
235                                return templateTextFields
236                        case TemplateFieldType.STRINGLIST:
237                                return templateStringListFields
[212]238                        case TemplateFieldType.INTEGER:
[236]239                                return templateIntegerFields
[212]240                        case TemplateFieldType.DATE:
[236]241                                return templateDateFields
[212]242                        case TemplateFieldType.FLOAT:
[236]243                                return templateFloatFields
[212]244                        case TemplateFieldType.DOUBLE:
[236]245                                return templateDoubleFields
[212]246                        case TemplateFieldType.ONTOLOGYTERM:
[236]247                                return templateTermFields
[313]248                        default:
[212]249                                throw new NoSuchFieldException("Field type ${fieldType} not recognized")
250                }
251        }
252
[236]253        /**
[406]254         * Find a field domain or template field by its name and return its description
255         * @param fieldsCollection the set of fields to search in, usually something like this.giveFields()
[392]256         * @param fieldName The name of the domain or template field
[406]257         * @return the TemplateField description of the field
[236]258         * @throws NoSuchFieldException If the field is not found or the field type is not supported
259         */
[409]260        private static TemplateField getField(List<TemplateField> fieldsCollection, String fieldName) {
[396]261                // escape the fieldName for easy matching
262                // (such escaped names are commonly used
263                // in the HTTP forms of this application)
[406]264                String escapedLowerCaseFieldName = fieldName.toLowerCase().replaceAll("([^a-z0-9])","_")
[396]265
266                // Find the target template field, if not found, throw an error
[409]267                TemplateField field = fieldsCollection.find { it.name.toLowerCase().replaceAll("([^a-z0-9])", "_") == escapedLowerCaseFieldName }
[396]268
[406]269                if (field) {
270                        return field
271                }
272                else {
[396]273                        throw new NoSuchFieldException("Field ${fieldName} not recognized")
[406]274                }
275        }
276
277        /**
278         * Find a domain or template field by its name and return its value for this entity
279         * @param fieldName The name of the domain or template field
280         * @return the value of the field (class depends on the field type)
281         * @throws NoSuchFieldException If the field is not found or the field type is not supported
282         */
283        def getFieldValue(String fieldName) {
284                TemplateField field = getField(this.giveFields(),fieldName)
285                if (isDomainField(field)) {
286                        return this[field.name]
287                }
288                else {
[396]289                        return getStore(field.type)[fieldName]
[392]290                }
[236]291        }
292
[290]293        /**
[370]294         * Check whether a given template field exists or not
295         * @param fieldName The name of the template field
296         * @return true if the given field exists and false otherwise
297         */
[406]298        boolean fieldExists(String fieldName) {
299                // getField should throw a NoSuchFieldException if the field does not exist
300                try {
301                        TemplateField field = getField(this.giveFields(),fieldName)
302                }
303                // so return false in that case
304                catch(NoSuchFieldException e) {
305                        return false
306                }
307                // otherwise, return true (but double check if field really is not null)
308                if (field) {
[390]309                        return true
[406]310                }
311                else {
[390]312                        return false
313                }
[370]314        }
315
316        /**
[290]317         * Set a template/entity field value
318         * @param fieldName The name of the template or entity field
319         * @param value The value to be set, this should align with the (template) field type, but there are some convenience setters
320         */
[212]321        def setFieldValue(String fieldName, value) {
[390]322
[406]323                TemplateField field = getField(this.giveFields(),fieldName)
[396]324
[406]325                // Convenience setter for template string list fields: find TemplateFieldListItem by name
326                if (field.type == TemplateFieldType.STRINGLIST && value && value.class == String) {
[409]327                        // Kees insensitive pattern matching ;)
328                        def escapedLowerCaseValue = value.toLowerCase().replaceAll("([^a-z0-9])", "_")
329                        value = field.listEntries.find {
330                                it.name.toLowerCase().replaceAll("([^a-z0-9])", "_") == escapedLowerCaseValue
331                        }
[406]332                }
[257]333
[406]334                // Convenience setter for dates: handle string values for date fields
335                if (field.type == TemplateFieldType.DATE && value && value.class == String) {
336                        // a string was given, attempt to transform it into a date instance
337                        // and -for now- assume the dd/mm/yyyy format
338                        def dateMatch = value =~ /^([0-9]{1,})([^0-9]{1,})([0-9]{1,})([^0-9]{1,})([0-9]{1,})((([^0-9]{1,})([0-9]{1,2}):([0-9]{1,2})){0,})/
339                        if (dateMatch.matches()) {
340                                // create limited 'autosensing' datetime parser
341                                // assume dd mm yyyy  or dd mm yy
342                                def parser = 'd' + dateMatch[0][2] + 'M' + dateMatch[0][4] + (((dateMatch[0][5] as int) > 999) ? 'yyyy' : 'yy')
[257]343
[406]344                                // add time as well?
345                                if (dateMatch[0][7] != null) {
346                                        parser += dateMatch[0][6] + 'HH:mm'
347                                }
[257]348
[406]349                                value = new Date().parse(parser, value)
[396]350                        }
[406]351                }
[257]352
[406]353                // Set the field value
354                if (isDomainField(field)) {
355                        this[field.name] = value
356                }
357                else {
358                        // Caution: this assumes that all template...Field Maps are already initialized (as is done now above as [:])
359                        // If that is ever changed, the results are pretty much unpredictable (random Java object pointers?)!
[396]360                        def store = getStore(field.type)
361                        if (!value && store[fieldName]) {
362                                println "removing " + ((super) ? super.class : '??') + " template field: " + fieldName
[335]363
[396]364                                // remove the item from the Map (if present)
365                                store.remove(fieldName)
366                        } else if (value) {
367                                println "setting " + ((super) ? super.class : '??') + " template field: " + fieldName + " ([" + value.toString() + "] of type [" + value.class + "])"
[335]368
[396]369                                // set value
370                                store[fieldName] = value
[224]371                        }
[212]372                }
[406]373
[396]374                return this
[212]375        }
376
[406]377        boolean isDomainField(TemplateField field) {
378                return this.giveDomainFields()*.name.contains(field.name)
379        }
380
381        boolean isDomainField(String fieldName) {
382                return this.giveDomainFields()*.name.contains(fieldName)
383        }
384
[313]385        /**
[397]386         * Return all fields defined in the underlying template and the built-in
387     * domain fields of this entity
388         */
[338]389        def List<TemplateField> giveFields() {
[392]390                return this.giveDomainFields() + this.giveTemplateFields();
[224]391        }
392
[392]393        /**
[397]394         * Return all templated fields defined in the underlying template of this entity
395         */
[392]396        def List<TemplateField> giveTemplateFields() {
397                return this.template.fields;
[384]398        }
399
[313]400        /**
401         * Return all relevant 'built-in' domain fields of the super class
[392]402         * @return List with DomainTemplateFields
[397]403     * @see TemplateField
[313]404         */
[392]405        abstract List<TemplateField> giveDomainFields()
406
[228]407        /**
[236]408         * Convenience method. Returns all unique templates used within a collection of TemplateEntities.
[228]409         */
410        static List<Template> giveTemplates(Set<TemplateEntity> entityCollection) {
411                return entityCollection*.template.unique();
412        }
413
[236]414        /**
415         * Convenience method. Returns the template used within a collection of TemplateEntities.
416         * @throws NoSuchFieldException when 0 or multiple templates are used in the collection
417         * @return The template used by all members of a collection
418         */
419        static Template giveSingleTemplate(Set<TemplateEntity> entityCollection) {
[228]420                def templates = giveTemplates(entityCollection);
421                if (templates.size() == 0) {
422                        throw new NoSuchFieldException("No templates found in collection!")
[332]423                } else if (templates.size() == 1) {
[228]424                        return templates[0];
[332]425                } else {
[228]426                        throw new NoSuchFieldException("Multiple templates found in collection!")
427                }
428        }
[212]429}
Note: See TracBrowser for help on using the repository browser.