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

Last change on this file since 507 was 507, checked in by roberth, 11 years ago

Implemented file upload template fields

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