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

Last change on this file since 370 was 370, checked in by roberth, 9 years ago

Updated studies list to handle the new data model.
Updated the bootstrap to contain some studies with attached events and persons in order to test the updated studies list
Added fieldExists method to TemplateEntity? to check whether a given field exists or not

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