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

Last change on this file since 338 was 338, checked in by keesvb, 12 years ago

changed TemplateEntity?.giveFields to reflect ordering as well, also applied ordering to TemplateFieldListItems?, updated Sandbox to display mouse template fields

  • Property svn:keywords set to Date Rev Author
File size: 11.7 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: 338 $
11 * $Author: keesvb $
12 * $Date: 2010-04-12 13:11:34 +0000 (ma, 12 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         * Set a template/entity field value
266         * @param fieldName The name of the template or entity field
267         * @param value The value to be set, this should align with the (template) field type, but there are some convenience setters
268         */
269        def setFieldValue(String fieldName, value) {
270                // First, search if there is an entity property with the given name, and if so, set that
271                if (this.properties.containsKey(fieldName)) {
272                        this[fieldName] = value                 
273                } else if (template == null) {
274                        // not the found, then it is a template field, so check if there is a template
275                        throw new NoSuchFieldException("Field ${fieldName} not found in class properties: template not set")
276                } else {
277                        // there is a template, check the template fields
278                        // Find the target template field, if not found, throw an error
279                        TemplateField field = this.template.fields.find { it.name == fieldName }
280
281                        if (field == null) {
282                                // no such field
283                                throw new NoSuchFieldException("Field ${fieldName} not found in class template fields")
284                        } else {
285                                // Set the value of the found template field
286                                // Convenience setter for template string list fields: find TemplateFieldListItem by name
287                                if (field.type == TemplateFieldType.STRINGLIST && value.class == String) {
288                                        // Kees insensitive pattern matching ;)
289                                        value = field.listEntries.find { it.name ==~ /(?i)($value)/ }
290                                }
291
292                                // Convenience setter for dates: handle string values for date fields
293                                if (field.type == TemplateFieldType.DATE && value.class == String) {
294                                        // a string was given, attempt to transform it into a date instance
295                                        // and -for now- assume the dd/mm/yyyy format
296                                        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,})/
297                                        if (dateMatch.matches()) {
298                                                // create limited 'autosensing' datetime parser
299                                                // assume dd mm yyyy  or dd mm yy
300                                                def parser = 'd' + dateMatch[0][2] + 'M' + dateMatch[0][4] + (((dateMatch[0][5] as int) > 999) ? 'yyyy' : 'yy')
301
302                                                // add time as well?
303                                                if (dateMatch[0][7] != null) {
304                                                        parser += dateMatch[0][6] + 'HH:mm'
305                                                }
306
307                                                value = new Date().parse(parser, value)
308                                        }
309                                }
310
311                                // Set the field value
312                                // Caution: this assumes that all template...Field Maps are already initialized (as is done now above as [:])
313                                // If that is ever changed, the results are pretty much unpredictable (random Java object pointers?)!
314                                def store = getStore(field.type)
315                                if (!value && store[ fieldName ]) {
316                                        println "removing " + ((super) ? super.class : '??') + " template field: " + fieldName
317
318                                        // remove the item from the Map (if present)
319                                        store.remove( fieldName )
320                                } else if (value) {
321                                        println "setting " + ((super) ? super.class : '??') + " template field: " + fieldName + " ([" + value.toString() + "] of type [" + value.class + "])"
322
323                                        // set value
324                                        store[ fieldName ] = value
325                                }
326                                return this
327                        }
328                }
329        }
330
331        /**
332        * Return all templated fields defined in the underlying template of this entity
333        */
334        def List<TemplateField> giveFields() {
335                return this.template.fields;
336        }
337
338        /**
339         * Return all relevant 'built-in' domain fields of the super class
340         * @return key-value pairs describing the built-in fields, with the names as keys and type (as TemplateFieldType) as values
341         */
342        def giveDomainFields() {
343                def fieldSet = [:];
344                if (super.hasProperty('name')) {
345                        fieldSet['name'] = TemplateFieldType.STRING;
346                }
347                return fieldSet;
348        }
349
350        // See revision 237 for ideas about initializing the different templateField Maps
351        // with tailored Maps that already contain the neccessary keys
352        /**
353         * Convenience method. Returns all unique templates used within a collection of TemplateEntities.
354         */
355        static List<Template> giveTemplates(Set<TemplateEntity> entityCollection) {
356                return entityCollection*.template.unique();
357        }
358
359        /**
360         * Convenience method. Returns the template used within a collection of TemplateEntities.
361         * @throws NoSuchFieldException when 0 or multiple templates are used in the collection
362         * @return The template used by all members of a collection
363         */
364        static Template giveSingleTemplate(Set<TemplateEntity> entityCollection) {
365                def templates = giveTemplates(entityCollection);
366                if (templates.size() == 0) {
367                        throw new NoSuchFieldException("No templates found in collection!")
368                } else if (templates.size() == 1) {
369                        return templates[0];
370                } else {
371                        throw new NoSuchFieldException("Multiple templates found in collection!")
372                }
373        }
374}
Note: See TracBrowser for help on using the repository browser.