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

Last change on this file since 497 was 497, checked in by roberth, 12 years ago

Implemented RELTIME parsing and showing in the wizard.
Also made TEXT templatefields to show a textarea

  • Property svn:keywords set to Date Rev Author
File size: 15.9 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: 497 $
11 * $Author: roberth $
12 * $Date: 2010-05-28 14:19:06 +0000 (vr, 28 mei 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
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.