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

Last change on this file since 506 was 506, checked in by keesvb, 11 years ago

changed giveDomainFields() implementations in TemplateEntity? to static

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