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

Last change on this file since 540 was 540, checked in by duh, 11 years ago
  • fixed indentation shite!!!
  • samples dev commit
  • Property svn:keywords set to Date Rev Author
File size: 19.3 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: 540 $
11 * $Author: duh $
12 * $Date: 2010-06-08 11:04:08 +0000 (di, 08 jun 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
24        // N.B. If you try to set Long.MIN_VALUE for a reltime field, an error will occur
25        // However, this will never occur in practice: this value represents 3 bilion centuries
26        Map templateRelTimeFields = [:] // Contains relative times in seconds
27        Map templateFileFields = [:] // Contains filenames
28        Map templateTermFields = [:]
29
30        static hasMany = [
31                templateStringFields: String,
32                templateTextFields: String,
33                templateStringListFields: TemplateFieldListItem,
34                templateIntegerFields: int,
35                templateFloatFields: float,
36                templateDoubleFields: double,
37                templateDateFields: Date,
38                templateTermFields: Term,
39                templateRelTimeFields: long,
40                templateFileFields: String,
41                systemFields: TemplateField
42        ]
43
44        static mapping = {
45                tablePerHierarchy false
46
47                templateTextFields type: 'text'
48        }
49
50        def fileService
51
52        /**
53         * Constraints
54         *
55         * All template fields have their own custom validator. Note that there
56         * currently is a lot of code repetition. Ideally we don't want this, but
57         * unfortunately due to scope issues we cannot re-use the code. So make
58         * sure to replicate any changes to all pieces of logic! Only commented
59         * the first occurrence of the logic, please refer to the templateStringFields
60         * validator if you require information about the validation logic...
61         */
62        static constraints = {
63                template(nullable: true, blank: true)
64                templateStringFields(validator: { fields, obj, errors ->
65                        // note that we only use 'fields' and 'errors', 'obj' is
66                        // merely here because it's the way the closure is called
67                        // by the validator...
68
69                        // define a boolean
70                        def error = false
71
72                        // iterate through fields
73                        fields.each { key, value ->
74                                // check if the value is of proper type
75                                if (value && value.class != String) {
76                                        // it's of some other type
77                                        try {
78                                                // try to cast it to the proper type
79                                                fields[key] = (value as String)
80                                        } catch (Exception e) {
81                                                // could not typecast properly, value is of improper type
82                                                // add error message
83                                                error = true
84                                                errors.rejectValue(
85                                                        'templateStringFields',
86                                                        'templateEntity.typeMismatch.string',
87                                                        [key, value.class] as Object[],
88                                                        'Property {0} must be of type String and is currently of type {1}'
89                                                )
90                                        }
91                                }
92                        }
93
94                        // got an error, or not?
95                        return (!error)
96                })
97                templateTextFields(validator: { fields, obj, errors ->
98                        def error = false
99                        fields.each { key, value ->
100                                if (value && value.class != String) {
101                                        try {
102                                                fields[key] = (value as String)
103                                        } catch (Exception e) {
104                                                error = true
105                                                errors.rejectValue(
106                                                        'templateTextFields',
107                                                        'templateEntity.typeMismatch.string',
108                                                        [key, value.class] as Object[],
109                                                        'Property {0} must be of type String and is currently of type {1}'
110                                                )
111                                        }
112                                }
113                        }
114                        return (!error)
115                })
116                templateStringListFields(validator: { fields, obj, errors ->
117                        def error = false
118                        fields.each { key, value ->
119                                if (value && value.class != TemplateFieldListItem) {
120                                        try {
121                                                fields[key] = (value as TemplateFieldListItem)
122                                        } catch (Exception e) {
123                                                error = true
124                                                errors.rejectValue(
125                                                        'templateIntegerFields',
126                                                        'templateEntity.typeMismatch.templateFieldListItem',
127                                                        [key, value.class] as Object[],
128                                                        'Property {0} must be of type TemplateFieldListItem and is currently of type {1}'
129                                                )
130                                        }
131                                }
132                        }
133                        return (!error)
134                })
135                templateIntegerFields(validator: { fields, obj, errors ->
136                        def error = false
137                        fields.each { key, value ->
138                                if (value && value.class != Integer) {
139                                        try {
140                                                fields[key] = (value as Integer)
141                                        } catch (Exception e) {
142                                                error = true
143                                                errors.rejectValue(
144                                                        'templateIntegerFields',
145                                                        'templateEntity.typeMismatch.integer',
146                                                        [key, value.class] as Object[],
147                                                        'Property {0} must be of type Integer and is currently of type {1}'
148                                                )
149                                        }
150                                }
151                        }
152                        return (!error)
153                })
154                templateFloatFields(validator: { fields, obj, errors ->
155                        def error = false
156                        fields.each { key, value ->
157                                if (value && value.class != Float) {
158                                        try {
159                                                fields[key] = (value as Float)
160                                        } catch (Exception e) {
161                                                error = true
162                                                errors.rejectValue(
163                                                        'templateFloatFields',
164                                                        'templateEntity.typeMismatch.float',
165                                                        [key, value.class] as Object[],
166                                                        'Property {0} must be of type Float and is currently of type {1}'
167                                                )
168                                        }
169                                }
170                        }
171                        return (!error)
172                })
173                templateDoubleFields(validator: { fields, obj, errors ->
174                        def error = false
175                        fields.each { key, value ->
176                                if (value && value.class != Double) {
177                                        try {
178                                                fields[key] = (value as Double)
179                                        } catch (Exception e) {
180                                                error = true
181                                                errors.rejectValue(
182                                                        'templateDoubleFields',
183                                                        'templateEntity.typeMismatch.double',
184                                                        [key, value.class] as Object[],
185                                                        'Property {0} must be of type Double and is currently of type {1}'
186                                                )
187                                        }
188                                }
189                        }
190                        return (!error)
191                })
192                templateDateFields(validator: { fields, obj, errors ->
193                        def error = false
194                        fields.each { key, value ->
195                                if (value && value.class != Date) {
196                                        try {
197                                                fields[key] = (value as Date)
198                                        } catch (Exception e) {
199                                                error = true
200                                                errors.rejectValue(
201                                                        'templateDateFields',
202                                                        'templateEntity.typeMismatch.date',
203                                                        [key, value.class] as Object[],
204                                                        'Property {0} must be of type Date and is currently of type {1}'
205                                                )
206                                        }
207                                }
208                        }
209                        return (!error)
210                })
211                templateRelTimeFields(validator: { fields, obj, errors ->
212                        def error = false
213                        fields.each { key, value ->
214                                if (value && value == Long.MIN_VALUE) {
215                                        error = true
216                                        errors.rejectValue(
217                                                'templateRelTimeFields',
218                                                'templateEntity.typeMismatch.reltime',
219                                                [key, value] as Object[],
220                                                'Value cannot be parsed for property {0}'
221                                        )
222                                } else if (value && value.class != long) {
223                                        try {
224                                                fields[key] = (value as long)
225                                        } catch (Exception e) {
226                                                error = true
227                                                errors.rejectValue(
228                                                        'templateRelTimeFields',
229                                                        'templateEntity.typeMismatch.reltime',
230                                                        [key, value.class] as Object[],
231                                                        'Property {0} must be of type long and is currently of type {1}'
232                                                )
233                                        }
234                                }
235                        }
236                        return (!error)
237                })
238                templateTermFields(validator: { fields, obj, errors ->
239                        def error = false
240                        fields.each { key, value ->
241                                if (value && value.class != Term) {
242                                        try {
243                                                fields[key] = (value as Term)
244                                        } catch (Exception e) {
245                                                error = true
246                                                errors.rejectValue(
247                                                        'templateTermFields',
248                                                        'templateEntity.typeMismatch.term',
249                                                        [key, value.class] as Object[],
250                                                        'Property {0} must be of type Term and is currently of type {1}'
251                                                )
252                                        }
253                                }
254                        }
255                        return (!error)
256                })
257                templateFileFields(validator: { fields, obj, errors ->
258                        // note that we only use 'fields' and 'errors', 'obj' is
259                        // merely here because it's the way the closure is called
260                        // by the validator...
261
262                        // define a boolean
263                        def error = false
264
265                        // iterate through fields
266                        fields.each { key, value ->
267                                // check if the value is of proper type
268                                if (value && value.class != String) {
269                                        // it's of some other type
270                                        try {
271                                                // try to cast it to the proper type
272                                                fields[key] = (value as String)
273
274                                                // Find the file on the system
275                                                // if it does not exist, the filename can
276                                                // not be entered
277
278                                        } catch (Exception e) {
279                                                // could not typecast properly, value is of improper type
280                                                // add error message
281                                                error = true
282                                                errors.rejectValue(
283                                                        'templateFileFields',
284                                                        'templateEntity.typeMismatch.string',
285                                                        [key, value.class] as Object[],
286                                                        'Property {0} must be of type String and is currently of type {1}'
287                                                )
288                                        }
289                                }
290                        }
291
292                        // got an error, or not?
293                        return (!error)
294                })
295        }
296
297        /**
298         * Get the proper templateFields Map for a specific field type
299         * @param TemplateFieldType
300         * @return pointer
301         * @visibility private
302         * @throws NoSuchFieldException
303         */
304        public Map getStore(TemplateFieldType fieldType) {
305                switch (fieldType) {
306                        case TemplateFieldType.STRING:
307                                return templateStringFields
308                        case TemplateFieldType.TEXT:
309                                return templateTextFields
310                        case TemplateFieldType.STRINGLIST:
311                                return templateStringListFields
312                        case TemplateFieldType.INTEGER:
313                                return templateIntegerFields
314                        case TemplateFieldType.DATE:
315                                return templateDateFields
316                        case TemplateFieldType.RELTIME:
317                                return templateRelTimeFields
318                        case TemplateFieldType.FILE:
319                                return templateFileFields
320                        case TemplateFieldType.FLOAT:
321                                return templateFloatFields
322                        case TemplateFieldType.DOUBLE:
323                                return templateDoubleFields
324                        case TemplateFieldType.ONTOLOGYTERM:
325                                return templateTermFields
326                        default:
327                                throw new NoSuchFieldException("Field type ${fieldType} not recognized")
328                }
329        }
330
331        /**
332         * Find a field domain or template field by its name and return its description
333         * @param fieldsCollection the set of fields to search in, usually something like this.giveFields()
334         * @param fieldName The name of the domain or template field
335         * @return the TemplateField description of the field
336         * @throws NoSuchFieldException If the field is not found or the field type is not supported
337         */
338        private static TemplateField getField(List<TemplateField> fieldsCollection, String fieldName) {
339                // escape the fieldName for easy matching
340                // (such escaped names are commonly used
341                // in the HTTP forms of this application)
342                String escapedLowerCaseFieldName = fieldName.toLowerCase().replaceAll("([^a-z0-9])", "_")
343
344                // Find the target template field, if not found, throw an error
345                TemplateField field = fieldsCollection.find { it.name.toLowerCase().replaceAll("([^a-z0-9])", "_") == escapedLowerCaseFieldName }
346
347                if (field) {
348                        return field
349                }
350                else {
351                        throw new NoSuchFieldException("Field ${fieldName} not recognized")
352                }
353        }
354
355        /**
356         * Find a domain or template field by its name and return its value for this entity
357         * @param fieldName The name of the domain or template field
358         * @return the value of the field (class depends on the field type)
359         * @throws NoSuchFieldException If the field is not found or the field type is not supported
360         */
361        def getFieldValue(String fieldName) {
362
363                if (isDomainField(fieldName)) {
364                        return this[fieldName]
365                }
366                else {
367                        TemplateField field = getField(this.giveTemplateFields(), fieldName)
368                        return getStore(field.type)[fieldName]
369                }
370
371        }
372
373        /**
374         * Check whether a given template field exists or not
375         * @param fieldName The name of the template field
376         * @return true if the given field exists and false otherwise
377         */
378        boolean fieldExists(String fieldName) {
379                // getField should throw a NoSuchFieldException if the field does not exist
380                try {
381                        TemplateField field = getField(this.giveFields(), fieldName)
382                        // return true if exception is not thrown (but double check if field really is not null)
383                        if (field) {
384                                return true
385                        }
386                        else {
387                                return false
388                        }
389                }
390                // if exception is thrown, return false
391                catch (NoSuchFieldException e) {
392                        return false
393                }
394        }
395
396        /**
397         * Set a template/entity field value
398         * @param fieldName The name of the template or entity field
399         * @param value The value to be set, this should align with the (template) field type, but there are some convenience setters
400         */
401        def setFieldValue(String fieldName, value) {
402                // get the template field
403                TemplateField field = getField(this.giveFields(), fieldName)
404
405                // Convenience setter for template string list fields: find TemplateFieldListItem by name
406                if (field.type == TemplateFieldType.STRINGLIST && value && value.class == String) {
407                        // Kees insensitive pattern matching ;)
408                        def escapedLowerCaseValue = value.toLowerCase().replaceAll("([^a-z0-9])", "_")
409                        value = field.listEntries.find {
410                                it.name.toLowerCase().replaceAll("([^a-z0-9])", "_") == escapedLowerCaseValue
411                        }
412                }
413
414                // Magic setter for dates: handle string values for date fields
415                if (field.type == TemplateFieldType.DATE && value && value.class == String) {
416                        // a string was given, attempt to transform it into a date instance
417                        // and -for now- assume the dd/mm/yyyy format
418                        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,})/
419                        if (dateMatch.matches()) {
420                                // create limited 'autosensing' datetime parser
421                                // assume dd mm yyyy  or dd mm yy
422                                def parser = 'd' + dateMatch[0][2] + 'M' + dateMatch[0][4] + (((dateMatch[0][5] as int) > 999) ? 'yyyy' : 'yy')
423
424                                // add time as well?
425                                if (dateMatch[0][7] != null) {
426                                        parser += dateMatch[0][6] + 'HH:mm'
427                                }
428
429                                value = new Date().parse(parser, value)
430                        }
431                }
432
433                // Magic setter for relative times: handle string values for relTime fields
434                //
435                if (field.type == TemplateFieldType.RELTIME && value != null && value.class == String) {
436                        // A string was given, attempt to transform it into a timespan
437                        // If it cannot be parsed, set the lowest possible value of Long.
438                        // The validator method will raise an error
439                        //
440                        // N.B. If you try to set Long.MIN_VALUE itself, an error will occur
441                        // However, this will never occur: this value represents 3 bilion centuries
442                        try {
443                                value = RelTime.parseRelTime(value).getValue();
444                        } catch (IllegalArgumentException e) {
445                                value = Long.MIN_VALUE;
446                        }
447                }
448
449                // Sometimes the fileService is not created yet
450                if (!fileService) {
451                        fileService = new FileService();
452                }
453
454                // Magic setter for files: handle values for file fields
455                //
456                // If NULL is given, the field value is emptied and the old file is removed
457                // If an empty string is given, the field value is kept as was
458                // If a file is given, it is moved to the right directory. Old files are deleted. If
459                //   the file does not exist, the field is kept
460                // If a string is given, it is supposed to be a file in the upload directory. If
461                //   it is different from the old one, the old one is deleted. If the file does not
462                //   exist, the old one is kept.
463                if (field.type == TemplateFieldType.FILE) {
464                        def currentFile = getFieldValue(field.name);
465
466                        if (value == null) {
467                                // If NULL is given, the field value is emptied and the old file is removed
468                                value = "";
469                                if (currentFile) {
470                                        fileService.delete(currentFile)
471                                }
472                        } else if (value.class == File) {
473                                // a file was given. Attempt to move it to the upload directory, and
474                                // afterwards, store the filename. If the file doesn't exist
475                                // or can't be moved, "" is returned
476                                value = fileService.moveFileToUploadDir(value);
477
478                                if (value) {
479                                        if (currentFile) {
480                                                fileService.delete(currentFile)
481                                        }
482                                } else {
483                                        value = currentFile;
484                                }
485                        } else if (value == "") {
486                                value = currentFile;
487                        } else {
488                                if (value != currentFile) {
489                                        if (fileService.fileExists(value)) {
490                                                // When a FILE field is filled, and a new file is set
491                                                // the existing file should be deleted
492                                                if (currentFile) {
493                                                        fileService.delete(currentFile)
494                                                }
495                                        } else {
496                                                // If the file does not exist, the field is kept
497                                                value = currentFile;
498                                        }
499                                }
500                        }
501                }
502
503                // Magic setter for ontology terms: handle string values
504                if (field.type == TemplateFieldType.ONTOLOGYTERM && value && value.class == String) {
505                        // iterate through ontologies and find term
506                        field.ontologies.each() { ontology ->
507                                def term = ontology.giveTermByName(value)
508
509                                // found a term?
510                                if (term) {
511                                        value = term
512                                }
513                        }
514                }
515
516                // Set the field value
517                if (isDomainField(field)) {
518                        // got a value?
519                        if (value) {
520                                println ".setting [" + ((super) ? super.class : '??') + "] domain field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
521
522                                // set value
523                                this[field.name] = value
524                        } else {
525                                println ".unsetting [" + ((super) ? super.class : '??') + "] domain field: [" + fieldName + "]"
526
527                                // remove value. For numbers, this is done by setting
528                                // the value to 0, otherwise, setting it to NULL
529                                switch (field.type.toString()) {
530                                        case ['INTEGER', 'FLOAT', 'DOUBLE', 'RELTIME']:
531                                                this[field.name] = 0;
532                                                break;
533                                        default:
534                                                this[field.name] = null
535                                }
536                        }
537                } else {
538                        // Caution: this assumes that all template...Field Maps are already initialized (as is done now above as [:])
539                        // If that is ever changed, the results are pretty much unpredictable (random Java object pointers?)!
540                        def store = getStore(field.type)
541
542                        // If some value is entered (or 0), then save the value
543                        // otherwise, it should not be present in the store, so
544                        // it is unset if it is.
545                        if (value || value == 0) {
546                                println ".setting [" + ((super) ? super.class : '??') + "] template field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
547
548                                // set value
549                                store[fieldName] = value
550                        } else if (store[fieldName]) {
551                                println ".unsetting [" + ((super) ? super.class : '??') + "] template field: [" + fieldName + "]"
552
553                                // remove the item from the Map (if present)
554                                store.remove(fieldName)
555                        }
556                }
557
558                return this
559        }
560
561        /**
562         * Check if a given field is a domain field
563         * @param TemplateField field instance
564         * @return boolean
565         */
566        boolean isDomainField(TemplateField field) {
567                return isDomainField(field.name)
568        }
569
570        /**
571         * Check if a given field is a domain field
572         * @param String field name
573         * @return boolean
574         */
575        boolean isDomainField(String fieldName) {
576                return this.giveDomainFields()*.name.contains(fieldName)
577        }
578
579        /**
580         * Return all fields defined in the underlying template and the built-in
581         * domain fields of this entity
582         */
583        def List<TemplateField> giveFields() {
584                return this.giveDomainFields() + this.giveTemplateFields();
585        }
586
587        /**
588         * Return all templated fields defined in the underlying template of this entity
589         */
590        def List<TemplateField> giveTemplateFields() {
591                return (this.template) ? this.template.fields : []
592        }
593
594        /**
595         * Return all relevant 'built-in' domain fields of the super class. Should be implemented by a static method
596         * @return List with DomainTemplateFields
597         * @see TemplateField
598         */
599        abstract List<TemplateField> giveDomainFields()
600
601        /**
602         * Convenience method. Returns all unique templates used within a collection of TemplateEntities.
603         *
604         * If the collection is empty, an empty set is returned. If none of the entities contains
605         * a template, also an empty set is returned.
606         */
607        static Set<Template> giveTemplates(Set<TemplateEntity> entityCollection) {
608                def set = entityCollection*.template.unique();
609
610                // If one or more entities does not have a template, the resulting
611                // set contains null. That is not what is meant.
612                return set.findAll { it != null };
613        }
614
615        /**
616         * Convenience method. Returns the template used within a collection of TemplateEntities.
617         * @throws NoSuchFieldException when 0 or multiple templates are used in the collection
618         * @return The template used by all members of a collection
619         */
620        static Template giveSingleTemplate(Set<TemplateEntity> entityCollection) {
621                def templates = giveTemplates(entityCollection);
622                if (templates.size() == 0) {
623                        throw new NoSuchFieldException("No templates found in collection!")
624                } else if (templates.size() == 1) {
625                        return templates[0];
626                } else {
627                        throw new NoSuchFieldException("Multiple templates found in collection!")
628                }
629        }
630}
Note: See TracBrowser for help on using the repository browser.