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

Last change on this file since 421 was 421, checked in by keesvb, 9 years ago

cleaned up BootStrap? and re-added Samples, updated study/show to also show sampling events in EventGroups?, added description field for Templates

  • Property svn:keywords set to Date Author Rev
File size: 13.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: 421 $
11 * $Author: keesvb $
12 * $Date: 2010-05-17 13:26:48 +0000 (ma, 17 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)
[410]302                        // return true if exception is not thrown (but double check if field really is not null)
303                        if (field) {
304                                return true
305                        }
306                        else {
307                                return false
308                        }
[406]309                }
[410]310                // if exception is thrown, return false
[406]311                catch(NoSuchFieldException e) {
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) {
[414]322                // get the template field
[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
[414]334                // Magic setter for dates: handle string values for date fields
[406]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
[414]353                // Magic setter for ontology terms: handle string values
354                if (field.type == TemplateFieldType.ONTOLOGYTERM && value && value.class == String) {
355                        // iterate through ontologies and find term
356                        field.ontologies.each() { ontology ->
357                                def term = ontology.giveTermByName(value)
358
359                                // found a term?
360                                if (term) {
361                                        value = term
362                                }
363                        }
364                }
365
[406]366                // Set the field value
367                if (isDomainField(field)) {
[414]368                        // got a value?
369                        if (value) {
370                                println "setting [" + ((super) ? super.class : '??') + "] domain field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
371
372                                // set value
373                                this[field.name] = value
374                        } else {
[413]375                                println "removing [" + ((super) ? super.class : '??') + "] domain field: [" + fieldName + "]"
376
377                                // remove value
378                                this[field.name] = null
379                        }
380                } else {
[406]381                        // Caution: this assumes that all template...Field Maps are already initialized (as is done now above as [:])
382                        // If that is ever changed, the results are pretty much unpredictable (random Java object pointers?)!
[396]383                        def store = getStore(field.type)
384                        if (!value && store[fieldName]) {
[413]385                                println "removing [" + ((super) ? super.class : '??') + "] template field: [" + fieldName + "]"
[335]386
[396]387                                // remove the item from the Map (if present)
388                                store.remove(fieldName)
389                        } else if (value) {
[413]390                                println "setting [" + ((super) ? super.class : '??') + "] template field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
[335]391
[396]392                                // set value
393                                store[fieldName] = value
[224]394                        }
[212]395                }
[406]396
[396]397                return this
[212]398        }
399
[413]400        /**
401         * Check if a given field is a domain field
[414]402         * @param TemplateField         field instance
[413]403         * @return boolean
404         */
[406]405        boolean isDomainField(TemplateField field) {
406                return this.giveDomainFields()*.name.contains(field.name)
407        }
408
[414]409        /**
410         * Check if a given field is a domain field
411         * @param String        field name
412         * @return boolean
413         */     boolean isDomainField(String fieldName) {
[406]414                return this.giveDomainFields()*.name.contains(fieldName)
415        }
416
[313]417        /**
[397]418         * Return all fields defined in the underlying template and the built-in
419     * domain fields of this entity
420         */
[338]421        def List<TemplateField> giveFields() {
[392]422                return this.giveDomainFields() + this.giveTemplateFields();
[224]423        }
424
[392]425        /**
[397]426         * Return all templated fields defined in the underlying template of this entity
427         */
[392]428        def List<TemplateField> giveTemplateFields() {
429                return this.template.fields;
[384]430        }
431
[313]432        /**
433         * Return all relevant 'built-in' domain fields of the super class
[392]434         * @return List with DomainTemplateFields
[397]435     * @see TemplateField
[313]436         */
[392]437        abstract List<TemplateField> giveDomainFields()
438
[228]439        /**
[236]440         * Convenience method. Returns all unique templates used within a collection of TemplateEntities.
[228]441         */
[421]442        static Set<Template> giveTemplates(Set<TemplateEntity> entityCollection) {
[228]443                return entityCollection*.template.unique();
444        }
445
[236]446        /**
447         * Convenience method. Returns the template used within a collection of TemplateEntities.
448         * @throws NoSuchFieldException when 0 or multiple templates are used in the collection
449         * @return The template used by all members of a collection
450         */
451        static Template giveSingleTemplate(Set<TemplateEntity> entityCollection) {
[228]452                def templates = giveTemplates(entityCollection);
453                if (templates.size() == 0) {
454                        throw new NoSuchFieldException("No templates found in collection!")
[332]455                } else if (templates.size() == 1) {
[228]456                        return templates[0];
[332]457                } else {
[228]458                        throw new NoSuchFieldException("Multiple templates found in collection!")
459                }
460        }
[414]461}
Note: See TracBrowser for help on using the repository browser.