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

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

switched back from the fancy 'integrate domain fields as system fields directly into the Template object at the time of creation' method to the plain 'implement getFieldValue, setFieldValue and giveFields to work for both domain and template fields'. NB: this might affect Events, since until now startTime and endTime were moved directly into template! Switch any Event.template.fields to Event.giveFields()

  • Property svn:keywords set to Date Author Rev
File size: 13.6 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: 392 $
11 * $Author: keesvb $
12 * $Date: 2010-05-04 15:03:21 +0000 (di, 04 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 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                systemFields: TemplateField
35        ]
36
37        static mapping = {
38                tablePerHierarchy false
39
40                templateTextFields type: 'text'
41        }       
42
43        /**
44         * Constraints
45         *
46         * All template fields have their own custom validator. Note that there
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...
52         */
53        static constraints = {
54                template(nullable: true, blank: true)
55                templateStringFields(validator: { fields, obj, errors ->
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
61                        def error = false
62
63                        // iterate through fields
64                        fields.each { key, value ->
65                                // check if the value is of proper type
66                                if ( value && value.class != String ) {
67                                        // it's of some other type
68                                        try {
69                                                // try to cast it to the proper type
70                                                fields[key] = (value as String)
71                                        } catch (Exception e) {
72                                                // could not typecast properly, value is of improper type
73                                                // add error message
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                        }
84
85                        // got an error, or not?
86                        return (!error)
87                })
88                templateTextFields(validator: { fields, obj, errors ->
89                        def error = false
90                        fields.each { key, value ->
91                                if ( value && value.class != String ) {
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 ->
110                                if ( value && value.class != TemplateFieldListItem ) {
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 ->
129                                if (value && value.class != Integer ) {
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 ->
148                                if ( value && value.class != Float ) {
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 ->
167                                if ( value && value.class != Double ) {
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 ->
186                                if ( value && value.class != Date ) {
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 ->
205                                if ( value && value.class != Term ) {
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                })
221        }
222
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         */
230        private Map getStore(TemplateFieldType fieldType) {
231                switch(fieldType) {
232                        case TemplateFieldType.STRING:
233                                return templateStringFields
234                        case TemplateFieldType.TEXT:
235                                return templateTextFields
236                        case TemplateFieldType.STRINGLIST:
237                                return templateStringListFields
238                        case TemplateFieldType.INTEGER:
239                                return templateIntegerFields
240                        case TemplateFieldType.DATE:
241                                return templateDateFields
242                        case TemplateFieldType.FLOAT:
243                                return templateFloatFields
244                        case TemplateFieldType.DOUBLE:
245                                return templateDoubleFields
246                        case TemplateFieldType.ONTOLOGYTERM:
247                                return templateTermFields
248                        default:
249                                throw new NoSuchFieldException("Field type ${fieldType} not recognized")
250                }
251        }
252
253        /**
254         * Find a domain or template field by its name and return its value for this entity
255         * @param fieldName The name of the domain or template field
256         * @return the value of the field (class depends on the field type)
257         * @throws NoSuchFieldException If the field is not found or the field type is not supported
258         */
259        def getFieldValue(String fieldName) {
260                if (this.properties.containsKey(fieldName)) {
261                        return this[fieldName]
262                }
263                else {
264                        TemplateFieldType fieldType = template.getFieldType(fieldName)
265                        if (!fieldType) throw new NoSuchFieldException("Field name ${fieldName} not recognized")
266                        return getStore(fieldType)[fieldName]
267                }
268        }
269
270        /**
271         * Check whether a given template field exists or not
272         * @param fieldName The name of the template field
273         * @return true if the given field exists and false otherwise
274         */
275        def fieldExists(String fieldName) {
276                // escape the fieldName for easy matching
277                // (such escaped names are commonly used
278                // in the HTTP forms of this application)
279                def escapedLowerCaseFieldName = fieldName.toLowerCase().replaceAll("([^a-z0-9])","_")
280
281                // check if this domain class has got this property
282                if (this.properties.containsKey(fieldName)) {
283                        // domain class contains this property
284                        return true
285                } else if (template == null) {
286                        // no, and we haven't got a template set either
287                        return false
288                } else {
289                        // the domain class doesn't have this property but
290                        // it has a template defined. Check the template
291                        // fields to see if such a template field exists
292                        TemplateField field = this.template.fields.find { it.name.toLowerCase().replaceAll("([^a-z0-9])","_") == escapedLowerCaseFieldName }
293
294                        // does the template field exist?
295                        if (field == null) {
296                                // no such template field
297                                return false
298                        } else {
299                                // found!
300                                return true
301                        }
302                }
303        }
304
305        /**
306         * Set a template/entity field value
307         * @param fieldName The name of the template or entity field
308         * @param value The value to be set, this should align with the (template) field type, but there are some convenience setters
309         */
310        def setFieldValue(String fieldName, value) {
311                // escape the fieldName for easy matching
312                // (such escaped names are commonly used
313                // in the HTTP forms of this application)
314                def escapedLowerCaseFieldName = fieldName.toLowerCase().replaceAll("([^a-z0-9])","_")
315
316                // First, search if there is an entity property with the given name, and if so, set that
317                if (this.properties.containsKey(fieldName)) {
318                        this[fieldName] = value                 
319                } else if (template == null) {
320                        // not the found, then it is a template field, so check if there is a template
321                        throw new NoSuchFieldException("Field ${fieldName} not found in class properties: template not set")
322                } else {
323                        // there is a template, check the template fields
324                        // Find the target template field, if not found, throw an error
325                        TemplateField field = this.template.fields.find { it.name.toLowerCase().replaceAll("([^a-z0-9])","_") == escapedLowerCaseFieldName }
326
327                        if (field == null) {
328                                // no such field
329                                throw new NoSuchFieldException("Field ${fieldName} not found in class template fields")
330                        } else {
331                                // Set the value of the found template field
332                                // Convenience setter for template string list fields: find TemplateFieldListItem by name
333                                if (field.type == TemplateFieldType.STRINGLIST && value && value.class == String) {
334                                        // Kees insensitive pattern matching ;)
335                                        def escapedLowerCaseValue = value.toLowerCase().replaceAll("([^a-z0-9])","_")
336                                        value = field.listEntries.find {
337                                                it.name.toLowerCase().replaceAll("([^a-z0-9])","_") == escapedLowerCaseValue
338                                        }
339                                }
340
341                                // Convenience setter for dates: handle string values for date fields
342                                if (field.type == TemplateFieldType.DATE && value.class == String) {
343                                        // a string was given, attempt to transform it into a date instance
344                                        // and -for now- assume the dd/mm/yyyy format
345                                        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,})/
346                                        if (dateMatch.matches()) {
347                                                // create limited 'autosensing' datetime parser
348                                                // assume dd mm yyyy  or dd mm yy
349                                                def parser = 'd' + dateMatch[0][2] + 'M' + dateMatch[0][4] + (((dateMatch[0][5] as int) > 999) ? 'yyyy' : 'yy')
350
351                                                // add time as well?
352                                                if (dateMatch[0][7] != null) {
353                                                        parser += dateMatch[0][6] + 'HH:mm'
354                                                }
355
356                                                value = new Date().parse(parser, value)
357                                        }
358                                }
359
360                                // Set the field value
361                                // Caution: this assumes that all template...Field Maps are already initialized (as is done now above as [:])
362                                // If that is ever changed, the results are pretty much unpredictable (random Java object pointers?)!
363                                def store = getStore(field.type)
364                                if (!value && store[ fieldName ]) {
365                                        println "removing " + ((super) ? super.class : '??') + " template field: " + fieldName
366
367                                        // remove the item from the Map (if present)
368                                        store.remove( fieldName )
369                                } else if (value) {
370                                        println "setting " + ((super) ? super.class : '??') + " template field: " + fieldName + " ([" + value.toString() + "] of type [" + value.class + "])"
371
372                                        // set value
373                                        store[ fieldName ] = value
374                                }
375                                return this
376                        }
377                }
378        }
379
380        /**
381        * Return all fields defined in the underlying template and the built-in
382        * domain fields of this entity
383        */
384        def List<TemplateField> giveFields() {
385                return this.giveDomainFields() + this.giveTemplateFields();
386        }
387
388        /**
389        * Return all templated fields defined in the underlying template of this entity
390        */
391        def List<TemplateField> giveTemplateFields() {
392                return this.template.fields;
393        }
394
395        /**
396         * Return all relevant 'built-in' domain fields of the super class
397         * @return List with DomainTemplateFields
398         * @see TemplateField
399         */
400        abstract List<TemplateField> giveDomainFields()
401
402        /*def giveDomainFields() {
403                def fieldSet = [:];
404                if (super.hasProperty('name')) {
405                        fieldSet['name'] = TemplateFieldType.STRING;
406                }
407                return fieldSet;
408        }*/
409
410        // See revision 237 for ideas about initializing the different templateField Maps
411        // with tailored Maps that already contain the neccessary keys
412        /**
413         * Convenience method. Returns all unique templates used within a collection of TemplateEntities.
414         */
415        static List<Template> giveTemplates(Set<TemplateEntity> entityCollection) {
416                return entityCollection*.template.unique();
417        }
418
419        /**
420         * Convenience method. Returns the template used within a collection of TemplateEntities.
421         * @throws NoSuchFieldException when 0 or multiple templates are used in the collection
422         * @return The template used by all members of a collection
423         */
424        static Template giveSingleTemplate(Set<TemplateEntity> entityCollection) {
425                def templates = giveTemplates(entityCollection);
426                if (templates.size() == 0) {
427                        throw new NoSuchFieldException("No templates found in collection!")
428                } else if (templates.size() == 1) {
429                        return templates[0];
430                } else {
431                        throw new NoSuchFieldException("Multiple templates found in collection!")
432                }
433        }
434}
Note: See TracBrowser for help on using the repository browser.