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

Last change on this file since 1264 was 1264, checked in by work@…, 11 years ago

resolves issue #233, validation of an object which inherits from TemplateEntity? does not result in an error when required templateFields are missing.

  • Property svn:keywords set to Author Date Rev
File size: 31.8 KB
RevLine 
[212]1package dbnp.studycapturing
2
3import dbnp.data.Term
[332]4import org.springframework.validation.FieldError
[212]5
[238]6/**
[754]7 * The TemplateEntity domain Class is a superclass for all template-enabled study capture entities, including
8 * Study, Subject, Sample and Event. This class provides functionality for storing the different TemplateField
9 * values and returning the combined list of 'domain fields' and 'template fields' of a TemplateEntity.
10 * For an explanation of those terms, see the Template class.
[238]11 *
[754]12 * @see dbnp.studycapturing.Template
13 *
[238]14 * Revision information:
15 * $Rev: 1264 $
16 * $Author: work@osx.eu $
17 * $Date: 2010-12-14 16:44:23 +0000 (di, 14 dec 2010) $
18 */
[783]19abstract class TemplateEntity extends Identity {
[1264]20        // The actual template of this TemplateEntity instance
[224]21        Template template
[754]22
23        // Maps for storing the different template field values
[803]24        Map templateStringFields        = [:]
25        Map templateTextFields          = [:]
[1245]26        Map templateStringListFields= [:]
[803]27        Map templateDoubleFields        = [:]
28        Map templateDateFields          = [:]
29        Map templateBooleanFields       = [:]
30        Map templateTemplateFields      = [:]
[825]31        Map templateModuleFields        = [:]
32        Map templateLongFields          = [:]
[538]33
[540]34        // N.B. If you try to set Long.MIN_VALUE for a reltime field, an error will occur
35        // However, this will never occur in practice: this value represents 3 bilion centuries
[803]36        Map templateRelTimeFields       = [:] // Contains relative times in seconds
37        Map templateFileFields          = [:] // Contains filenames
38        Map templateTermFields          = [:]
[212]39
[778]40        // define relationships
[212]41        static hasMany = [
[825]42                templateStringFields    : String,
[1245]43                templateTextFields              : String,
[224]44                templateStringListFields: TemplateFieldListItem,
[825]45                templateDoubleFields    : double,
[1245]46                templateDateFields              : Date,
47                templateTermFields              : Term,
[825]48                templateRelTimeFields   : long,
[1245]49                templateFileFields              : String,
[825]50                templateBooleanFields   : boolean,
51                templateTemplateFields  : Template,
52                templateModuleFields    : AssayModule,
[1245]53                templateLongFields              : long,
54                systemFields                    : TemplateField
[212]55        ]
56
[1264]57        // remember required fields when
58        // so we can validate is the required
59        // template fields are set
60        boolean requiredFieldsSet       = false
61        Set requiredFields                      = []
62
63        /**
64         * Get the required fields for the defined template, currently
65         * this method is called in custom validators below but it's
66         * best to call it in a template setter method. But as that
67         * involves a lot of refactoring this implementation will do
68         * fine for now.
69         *
70         * Another possible issue might be that if the template is
71         * updated after the required fields are cached in the object.
72         *
73         * @return Set  requiredFields
74         */
75        final Set getRequiredFields() {
76                if (template) {
77                        // template is set, check if required fields
78                        // have already been fetched
79                        if (!requiredFieldsSet) {
80                                // no, fetch required fields
81                                requiredFields          = template.getRequiredFields()
82                                requiredFieldsSet       = true
83                        }
84
85                        // return the required fields
86                        return requiredFields
87                } else {
88                        // template is not yet set, return
89                        // an empty set
90                        return []
91                }
92        }
93
94        // overload transients from Identity
95        // and append requiredFields vars
96        static transients                       = [ "identifier", "iterator", "maximumIdentity", "requiredFieldsSet", "requiredFields" ]
97
98        // define the mapping
[236]99        static mapping = {
[754]100                // Specify that each TemplateEntity-subclassing entity should have its own tables to store TemplateField values.
101                // This results in a lot of tables, but performance is presumably better because in most queries, only values of
102                // one specific entity will be retrieved. Also, because of the generic nature of these tables, they can end up
103                // containing a lot of records (there is a record for each entity instance for each property, instead of a record
104                // for each instance as is the case with 'normal' straightforward database tables. Therefore, it's better to split
105                // out the data to many tables.
[236]106                tablePerHierarchy false
107
[754]108                // Make sure that the text fields are really stored as TEXT, so that those Strings can have an arbitrary length.
[236]109                templateTextFields type: 'text'
[500]110        }
[332]111
[754]112        // Inject the service for storing files (for TemplateFields of TemplateFieldType FILE).
[540]113        def fileService
[507]114
[332]115        /**
[335]116         * Constraints
[332]117         *
118         * All template fields have their own custom validator. Note that there
[335]119         * currently is a lot of code repetition. Ideally we don't want this, but
120         * unfortunately due to scope issues we cannot re-use the code. So make
121         * sure to replicate any changes to all pieces of logic! Only commented
122         * the first occurrence of the logic, please refer to the templateStringFields
123         * validator if you require information about the validation logic...
[332]124         */
125        static constraints = {
126                template(nullable: true, blank: true)
127                templateStringFields(validator: { fields, obj, errors ->
[335]128                        // note that we only use 'fields' and 'errors', 'obj' is
129                        // merely here because it's the way the closure is called
130                        // by the validator...
131
132                        // define a boolean
[332]133                        def error = false
134
135                        // iterate through fields
136                        fields.each { key, value ->
[335]137                                // check if the value is of proper type
[1167]138                                if (value) {
139                                        def strValue = "";
140                                        if( value.class != String) {
141                                                // it's of some other type
142                                                try {
143                                                        // try to cast it to the proper type
144                                                        strValue = (value as String)
145                                                        fields[key] = strValue
146                                                } catch (Exception e) {
147                                                        // could not typecast properly, value is of improper type
148                                                        // add error message
149                                                        error = true
150                                                        errors.rejectValue(
151                                                                'templateStringFields',
152                                                                'templateEntity.typeMismatch.string',
153                                                                [key, value.class] as Object[],
154                                                                'Property {0} must be of type String and is currently of type {1}'
155                                                        )
156                                                }
157                                        } else {
158                                                strValue = value;
159                                        }
160
161                                        // Check whether the string doesn't exceed 255 characters
162                                        if( strValue.size() > 255 ) {
[332]163                                                error = true
164                                                errors.rejectValue(
165                                                        'templateStringFields',
[1167]166                                                        'templateEntity.tooLong.string',
167                                                        [key] as Object[],
168                                                        'Property {0} may contain at most 255 characters.'
[332]169                                                )
170                                        }
171                                }
172                        }
[335]173
[1264]174                        // validating the required template fields. Note that the
175                        // getRequiredFields() are cached in this object, so any
176                        // template changes after caching may not be validated
177                        // properly.
178                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.STRING }.each { field ->
179                                if (! fields.find { key, value -> key == field.name } ) {
180                                        // required field is missing
181                                        error = true
182                                        errors.rejectValue(
183                                                'templateStringFields',
184                                                'templateEntity.required',
185                                                [field.name] as Object[],
186                                                'Property {0} is required but it missing'
187                                        )
188                                }
189                        }
190
[335]191                        // got an error, or not?
[332]192                        return (!error)
193                })
194                templateTextFields(validator: { fields, obj, errors ->
195                        def error = false
196                        fields.each { key, value ->
[500]197                                if (value && value.class != String) {
[332]198                                        try {
199                                                fields[key] = (value as String)
200                                        } catch (Exception e) {
201                                                error = true
202                                                errors.rejectValue(
203                                                        'templateTextFields',
204                                                        'templateEntity.typeMismatch.string',
205                                                        [key, value.class] as Object[],
206                                                        'Property {0} must be of type String and is currently of type {1}'
207                                                )
208                                        }
209                                }
210                        }
[1264]211
212                        // validating required fields
213                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.TEXT }.each { field ->
214                                if (! fields.find { key, value -> key == field.name } ) {
215                                        // required field is missing
216                                        error = true
217                                        errors.rejectValue(
218                                                'templateTextFields',
219                                                'templateEntity.required',
220                                                [field.name] as Object[],
221                                                'Property {0} is required but it missing'
222                                        )
223                                }
224                        }
225
[332]226                        return (!error)
227                })
228                templateStringListFields(validator: { fields, obj, errors ->
229                        def error = false
230                        fields.each { key, value ->
[500]231                                if (value && value.class != TemplateFieldListItem) {
[332]232                                        try {
233                                                fields[key] = (value as TemplateFieldListItem)
234                                        } catch (Exception e) {
235                                                error = true
236                                                errors.rejectValue(
[1153]237                                                        'templateStringFields',
[332]238                                                        'templateEntity.typeMismatch.templateFieldListItem',
239                                                        [key, value.class] as Object[],
240                                                        'Property {0} must be of type TemplateFieldListItem and is currently of type {1}'
241                                                )
242                                        }
243                                }
244                        }
[1264]245
246                        // validating required fields
247                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.STRINGLIST }.each { field ->
248                                if (! fields.find { key, value -> key == field.name } ) {
249                                        // required field is missing
250                                        error = true
251                                        errors.rejectValue(
252                                                'templateStringFields',
253                                                'templateEntity.required',
254                                                [field.name] as Object[],
255                                                'Property {0} is required but it missing'
256                                        )
257                                }
258                        }
259
[332]260                        return (!error)
261                })
262                templateDoubleFields(validator: { fields, obj, errors ->
263                        def error = false
264                        fields.each { key, value ->
[1153]265                                // Double field should accept Doubles and Floats
[500]266                                if (value && value.class != Double) {
[1153]267                                        if(value.class == Float) {
268                                                fields[key] = value.toDouble();
269                                        } else {
270                                                try {
271                                                        fields[key] = (value as Double)
272                                                } catch (Exception e) {
273                                                        error = true
274                                                        errors.rejectValue(
275                                                                'templateDoubleFields',
276                                                                'templateEntity.typeMismatch.double',
277                                                                [key, value.class] as Object[],
278                                                                'Property {0} must be of type Double and is currently of type {1}'
279                                                        )
280                                                }
[332]281                                        }
282                                }
283                        }
[1264]284
285                        // validating required fields
286                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.DOUBLE }.each { field ->
287                                if (! fields.find { key, value -> key == field.name } ) {
288                                        // required field is missing
289                                        error = true
290                                        errors.rejectValue(
291                                                'templateDoubleFields',
292                                                'templateEntity.required',
293                                                [field.name] as Object[],
294                                                'Property {0} is required but it missing'
295                                        )
296                                }
297                        }
298
[332]299                        return (!error)
300                })
301                templateDateFields(validator: { fields, obj, errors ->
302                        def error = false
303                        fields.each { key, value ->
[500]304                                if (value && value.class != Date) {
[332]305                                        try {
306                                                fields[key] = (value as Date)
307                                        } catch (Exception e) {
308                                                error = true
309                                                errors.rejectValue(
310                                                        'templateDateFields',
311                                                        'templateEntity.typeMismatch.date',
312                                                        [key, value.class] as Object[],
313                                                        'Property {0} must be of type Date and is currently of type {1}'
314                                                )
315                                        }
316                                }
317                        }
[1264]318
319                        // validating required fields
320                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.DATE }.each { field ->
321                                if (! fields.find { key, value -> key == field.name } ) {
322                                        // required field is missing
323                                        error = true
324                                        errors.rejectValue(
325                                                'templateDateFields',
326                                                'templateEntity.required',
327                                                [field.name] as Object[],
328                                                'Property {0} is required but it missing'
329                                        )
330                                }
331                        }
332
[332]333                        return (!error)
334                })
[487]335                templateRelTimeFields(validator: { fields, obj, errors ->
336                        def error = false
337                        fields.each { key, value ->
[540]338                                if (value && value == Long.MIN_VALUE) {
339                                        error = true
340                                        errors.rejectValue(
341                                                'templateRelTimeFields',
342                                                'templateEntity.typeMismatch.reltime',
343                                                [key, value] as Object[],
344                                                'Value cannot be parsed for property {0}'
345                                        )
346                                } else if (value && value.class != long) {
[487]347                                        try {
348                                                fields[key] = (value as long)
349                                        } catch (Exception e) {
350                                                error = true
351                                                errors.rejectValue(
352                                                        'templateRelTimeFields',
353                                                        'templateEntity.typeMismatch.reltime',
354                                                        [key, value.class] as Object[],
355                                                        'Property {0} must be of type long and is currently of type {1}'
356                                                )
357                                        }
358                                }
359                        }
[1264]360
361                        // validating required fields
362                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.RELTIME }.each { field ->
363                                if (! fields.find { key, value -> key == field.name } ) {
364                                        // required field is missing
365                                        error = true
366                                        errors.rejectValue(
367                                                'templateRelTimeFields',
368                                                'templateEntity.required',
369                                                [field.name] as Object[],
370                                                'Property {0} is required but it missing'
371                                        )
372                                }
373                        }
374
[487]375                        return (!error)
376                })
[332]377                templateTermFields(validator: { fields, obj, errors ->
378                        def error = false
379                        fields.each { key, value ->
[500]380                                if (value && value.class != Term) {
[332]381                                        try {
382                                                fields[key] = (value as Term)
383                                        } catch (Exception e) {
384                                                error = true
385                                                errors.rejectValue(
386                                                        'templateTermFields',
387                                                        'templateEntity.typeMismatch.term',
388                                                        [key, value.class] as Object[],
389                                                        'Property {0} must be of type Term and is currently of type {1}'
390                                                )
391                                        }
392                                }
393                        }
[1264]394
395                        // validating required fields
396                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.ONTOLOGYTERM }.each { field ->
397                                if (! fields.find { key, value -> key == field.name } ) {
398                                        // required field is missing
399                                        error = true
400                                        errors.rejectValue(
401                                                'templateTermFields',
402                                                'templateEntity.required',
403                                                [field.name] as Object[],
404                                                'Property {0} is required but it missing'
405                                        )
406                                }
407                        }
408
[332]409                        return (!error)
410                })
[507]411                templateFileFields(validator: { fields, obj, errors ->
412                        // note that we only use 'fields' and 'errors', 'obj' is
413                        // merely here because it's the way the closure is called
414                        // by the validator...
415
416                        // define a boolean
417                        def error = false
418
419                        // iterate through fields
420                        fields.each { key, value ->
421                                // check if the value is of proper type
422                                if (value && value.class != String) {
423                                        // it's of some other type
424                                        try {
425                                                // try to cast it to the proper type
426                                                fields[key] = (value as String)
427
[540]428                                                // Find the file on the system
429                                                // if it does not exist, the filename can
430                                                // not be entered
431
[507]432                                        } catch (Exception e) {
433                                                // could not typecast properly, value is of improper type
434                                                // add error message
435                                                error = true
436                                                errors.rejectValue(
437                                                        'templateFileFields',
[803]438                                                        'templateEntity.typeMismatch.file',
[507]439                                                        [key, value.class] as Object[],
440                                                        'Property {0} must be of type String and is currently of type {1}'
441                                                )
442                                        }
443                                }
444                        }
445
[1264]446                        // validating required fields
447                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.FILE }.each { field ->
448                                if (! fields.find { key, value -> key == field.name } ) {
449                                        // required field is missing
450                                        error = true
451                                        errors.rejectValue(
452                                                'templateFileFields',
453                                                'templateEntity.required',
454                                                [field.name] as Object[],
455                                                'Property {0} is required but it missing'
456                                        )
457                                }
458                        }
459
[507]460                        // got an error, or not?
461                        return (!error)
462                })
[825]463                templateBooleanFields(validator: { fields, obj, errors ->
464                        def error = false
465                        fields.each { key, value ->
466                                if (value) {
467                                        fields[key] = true;
468                                } else {
469                                        fields[key] = false;
470                                }
471                        }
[1264]472
[825]473                        return (!error)
474                })
[803]475                templateTemplateFields(validator: { fields, obj, errors ->
476                        def error = false
477                        fields.each { key, value ->
478                                if (value && value.class != Template) {
479                                        try {
480                                                fields[key] = (value as Template)
481                                        } catch (Exception e) {
482                                                error = true
483                                                errors.rejectValue(
484                                                        'templateTemplateFields',
485                                                        'templateEntity.typeMismatch.template',
486                                                        [key, value.class] as Object[],
487                                                        'Property {0} must be of type Template and is currently of type {1}'
488                                                )
489                                        }
490                                }
491                        }
[1264]492
493                        // validating required fields
494                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.TEMPLATE }.each { field ->
495                                if (! fields.find { key, value -> key == field.name } ) {
496                                        // required field is missing
497                                        error = true
498                                        errors.rejectValue(
499                                                'templateTemplateFields',
500                                                'templateEntity.required',
501                                                [field.name] as Object[],
502                                                'Property {0} is required but it missing'
503                                        )
504                                }
505                        }
506
[803]507                        return (!error)
508                })
[825]509                templateModuleFields(validator: { fields, obj, errors ->
[559]510                        def error = false
511                        fields.each { key, value ->
[825]512                                if (value && value.class != AssayModule) {
513                                        try {
514                                                fields[key] = (value as AssayModule)
515                                        } catch (Exception e) {
516                                                error = true
517                                                errors.rejectValue(
518                                                        'templateModuleFields',
519                                                        'templateEntity.typeMismatch.module',
520                                                        [key, value.class] as Object[],
521                                                        'Property {0} must be of type AssayModule and is currently of type {1}'
522                                                )
523                                        }
[559]524                                }
525                        }
[1264]526
527                        // validating required fields
528                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.MODULE }.each { field ->
529                                if (! fields.find { key, value -> key == field.name } ) {
530                                        // required field is missing
531                                        error = true
532                                        errors.rejectValue(
533                                                'templateModuleFields',
534                                                'templateEntity.required',
535                                                [field.name] as Object[],
536                                                'Property {0} is required but it missing'
537                                        )
538                                }
539                        }
540
[559]541                        return (!error)
542                })
[825]543                templateLongFields(validator: { fields, obj, errors ->
544                        def error = false
545                        fields.each { key, value ->
[1153]546                                // Long field should accept Longs and Integers
547                                if (value && value.class != Long ) {
548                                        if( value.class == Integer ) {
549                                                fields[key] = value.toLong();
550                                        } else {
551                                                try {
552                                                        fields[key] = Long.parseLong(value.trim())
553                                                } catch (Exception e) {
554                                                        error = true
555                                                        errors.rejectValue(
556                                                                'templateLongFields',
557                                                                'templateEntity.typeMismatch.long',
558                                                                [key, value.class] as Object[],
559                                                                'Property {0} must be of type Long and is currently of type {1}'
560                                                        )
561                                                }
[825]562                                        }
563                                }
564                        }
[1264]565
566                        // validating required fields
567                        obj.getRequiredFields().findAll { it.type == TemplateFieldType.LONG }.each { field ->
568                                if (! fields.find { key, value -> key == field.name } ) {
569                                        // required field is missing
570                                        error = true
571                                        errors.rejectValue(
572                                                'templateLongFields',
573                                                'templateEntity.required',
574                                                [field.name] as Object[],
575                                                'Property {0} is required but it missing'
576                                        )
577                                }
578                        }
579
[825]580                        return (!error)
581                })
[236]582        }
583
[335]584        /**
585         * Get the proper templateFields Map for a specific field type
586         * @param TemplateFieldType
587         * @return pointer
588         * @visibility private
589         * @throws NoSuchFieldException
590         */
[487]591        public Map getStore(TemplateFieldType fieldType) {
[500]592                switch (fieldType) {
[236]593                        case TemplateFieldType.STRING:
594                                return templateStringFields
595                        case TemplateFieldType.TEXT:
596                                return templateTextFields
597                        case TemplateFieldType.STRINGLIST:
598                                return templateStringListFields
[212]599                        case TemplateFieldType.DATE:
[236]600                                return templateDateFields
[487]601                        case TemplateFieldType.RELTIME:
602                                return templateRelTimeFields
[507]603                        case TemplateFieldType.FILE:
604                                return templateFileFields
[212]605                        case TemplateFieldType.DOUBLE:
[236]606                                return templateDoubleFields
[212]607                        case TemplateFieldType.ONTOLOGYTERM:
[236]608                                return templateTermFields
[559]609                        case TemplateFieldType.BOOLEAN:
610                                return templateBooleanFields
[803]611                        case TemplateFieldType.TEMPLATE:
612                                return templateTemplateFields
[825]613                        case TemplateFieldType.MODULE:
614                                return templateModuleFields
615                        case TemplateFieldType.LONG:
616                                return templateLongFields
[500]617                        default:
[212]618                                throw new NoSuchFieldException("Field type ${fieldType} not recognized")
619                }
620        }
621
[236]622        /**
[406]623         * Find a field domain or template field by its name and return its description
624         * @param fieldsCollection the set of fields to search in, usually something like this.giveFields()
[392]625         * @param fieldName The name of the domain or template field
[406]626         * @return the TemplateField description of the field
[236]627         * @throws NoSuchFieldException If the field is not found or the field type is not supported
628         */
[409]629        private static TemplateField getField(List<TemplateField> fieldsCollection, String fieldName) {
[396]630                // escape the fieldName for easy matching
631                // (such escaped names are commonly used
632                // in the HTTP forms of this application)
[500]633                String escapedLowerCaseFieldName = fieldName.toLowerCase().replaceAll("([^a-z0-9])", "_")
[396]634
635                // Find the target template field, if not found, throw an error
[409]636                TemplateField field = fieldsCollection.find { it.name.toLowerCase().replaceAll("([^a-z0-9])", "_") == escapedLowerCaseFieldName }
[396]637
[406]638                if (field) {
639                        return field
640                }
641                else {
[396]642                        throw new NoSuchFieldException("Field ${fieldName} not recognized")
[406]643                }
644        }
645
646        /**
647         * Find a domain or template field by its name and return its value for this entity
648         * @param fieldName The name of the domain or template field
649         * @return the value of the field (class depends on the field type)
650         * @throws NoSuchFieldException If the field is not found or the field type is not supported
651         */
652        def getFieldValue(String fieldName) {
[453]653
654                if (isDomainField(fieldName)) {
[500]655                        return this[fieldName]
[406]656                }
657                else {
[500]658                        TemplateField field = getField(this.giveTemplateFields(), fieldName)
659                        return getStore(field.type)[fieldName]
[392]660                }
[236]661        }
662
[290]663        /**
[370]664         * Check whether a given template field exists or not
665         * @param fieldName The name of the template field
666         * @return true if the given field exists and false otherwise
667         */
[406]668        boolean fieldExists(String fieldName) {
669                // getField should throw a NoSuchFieldException if the field does not exist
670                try {
[500]671                        TemplateField field = getField(this.giveFields(), fieldName)
[410]672                        // return true if exception is not thrown (but double check if field really is not null)
673                        if (field) {
674                                return true
675                        }
676                        else {
677                                return false
678                        }
[406]679                }
[410]680                // if exception is thrown, return false
[500]681                catch (NoSuchFieldException e) {
[406]682                        return false
683                }
[370]684        }
685
686        /**
[290]687         * Set a template/entity field value
688         * @param fieldName The name of the template or entity field
689         * @param value The value to be set, this should align with the (template) field type, but there are some convenience setters
690         */
[212]691        def setFieldValue(String fieldName, value) {
[414]692                // get the template field
[500]693                TemplateField field = getField(this.giveFields(), fieldName)
[396]694
[561]695                // Convenience setter for boolean fields
696                if( field.type == TemplateFieldType.BOOLEAN && value && value.class == String ) {
[725]697                        def lower = value.toLowerCase()
698                        if (lower.equals("true") || lower.equals("on") || lower.equals("x")) {
[561]699                                value = true
700                        }
[725]701                        else if (lower.equals("false") || lower.equals("off") || lower.equals("")) {
[561]702                                value = false
703                        }
704                        else {
705                                throw new IllegalArgumentException("Boolean string not recognized: ${value} when setting field ${fieldName}")
706                        }
[559]707                }
708
[406]709                // Convenience setter for template string list fields: find TemplateFieldListItem by name
710                if (field.type == TemplateFieldType.STRINGLIST && value && value.class == String) {
[409]711                        def escapedLowerCaseValue = value.toLowerCase().replaceAll("([^a-z0-9])", "_")
712                        value = field.listEntries.find {
713                                it.name.toLowerCase().replaceAll("([^a-z0-9])", "_") == escapedLowerCaseValue
714                        }
[406]715                }
[257]716
[414]717                // Magic setter for dates: handle string values for date fields
[406]718                if (field.type == TemplateFieldType.DATE && value && value.class == String) {
719                        // a string was given, attempt to transform it into a date instance
720                        // and -for now- assume the dd/mm/yyyy format
721                        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,})/
722                        if (dateMatch.matches()) {
723                                // create limited 'autosensing' datetime parser
724                                // assume dd mm yyyy  or dd mm yy
725                                def parser = 'd' + dateMatch[0][2] + 'M' + dateMatch[0][4] + (((dateMatch[0][5] as int) > 999) ? 'yyyy' : 'yy')
[257]726
[406]727                                // add time as well?
728                                if (dateMatch[0][7] != null) {
[881]729                                        parser += dateMatch[0][8] + 'HH:mm'
[406]730                                }
[257]731
[406]732                                value = new Date().parse(parser, value)
[396]733                        }
[406]734                }
[257]735
[487]736                // Magic setter for relative times: handle string values for relTime fields
[500]737                //
[497]738                if (field.type == TemplateFieldType.RELTIME && value != null && value.class == String) {
[540]739                        // A string was given, attempt to transform it into a timespan
740                        // If it cannot be parsed, set the lowest possible value of Long.
741                        // The validator method will raise an error
742                        //
743                        // N.B. If you try to set Long.MIN_VALUE itself, an error will occur
744                        // However, this will never occur: this value represents 3 bilion centuries
745                        try {
746                                value = RelTime.parseRelTime(value).getValue();
747                        } catch (IllegalArgumentException e) {
748                                value = Long.MIN_VALUE;
749                        }
[487]750                }
751
[540]752                // Sometimes the fileService is not created yet
753                if (!fileService) {
754                        fileService = new FileService();
755                }
[507]756
757                // Magic setter for files: handle values for file fields
[540]758                //
[1182]759                // If NULL is given or "*deleted*", the field value is emptied and the old file is removed
[540]760                // If an empty string is given, the field value is kept as was
761                // If a file is given, it is moved to the right directory. Old files are deleted. If
762                //   the file does not exist, the field is kept
763                // If a string is given, it is supposed to be a file in the upload directory. If
764                //   it is different from the old one, the old one is deleted. If the file does not
765                //   exist, the old one is kept.
[507]766                if (field.type == TemplateFieldType.FILE) {
[540]767                        def currentFile = getFieldValue(field.name);
[507]768
[1182]769                        if (value == null || ( value.class == String && value == '*deleted*' ) ) {
[540]770                                // If NULL is given, the field value is emptied and the old file is removed
771                                value = "";
772                                if (currentFile) {
773                                        fileService.delete(currentFile)
774                                }
775                        } else if (value.class == File) {
776                                // a file was given. Attempt to move it to the upload directory, and
777                                // afterwards, store the filename. If the file doesn't exist
778                                // or can't be moved, "" is returned
779                                value = fileService.moveFileToUploadDir(value);
[507]780
[540]781                                if (value) {
782                                        if (currentFile) {
783                                                fileService.delete(currentFile)
784                                        }
785                                } else {
786                                        value = currentFile;
787                                }
788                        } else if (value == "") {
789                                value = currentFile;
790                        } else {
791                                if (value != currentFile) {
792                                        if (fileService.fileExists(value)) {
793                                                // When a FILE field is filled, and a new file is set
794                                                // the existing file should be deleted
795                                                if (currentFile) {
796                                                        fileService.delete(currentFile)
797                                                }
798                                        } else {
799                                                // If the file does not exist, the field is kept
800                                                value = currentFile;
801                                        }
802                                }
803                        }
[507]804                }
805
[414]806                // Magic setter for ontology terms: handle string values
807                if (field.type == TemplateFieldType.ONTOLOGYTERM && value && value.class == String) {
808                        // iterate through ontologies and find term
809                        field.ontologies.each() { ontology ->
[1175]810                                // If we've found a term already, value.class == Term. In that case,
811                                // we shouldn't look further. Unfortunately, groovy doesn't support breaking out of
812                                // each(), so we check it on every iteration.
813                                if( value.class == String ) {
814                                        def term = ontology.giveTermByName(value)
[414]815
[1175]816                                        // found a term?
817                                        if (term) {
818                                                value = term
819                                        }
[414]820                                }
[1175]821                        }
822
823                        // If the term is not found in any ontology
824                        if( value.class == String ) {
[897]825                                // TODO: search ontology for the term online (it may still exist) and insert it into the Term cache
826                                // if not found, throw exception
[1175]827                                throw new IllegalArgumentException("Ontology term not recognized (not in the GSCF ontology cache): ${value} when setting field ${fieldName}")
[414]828                        }
829                }
830
[825]831                // Magic setter for TEMPLATE fields
[803]832                if (field.type == TemplateFieldType.TEMPLATE && value && value.class == String) {
833                        value = Template.findByName(value)
834                }
835
[825]836                // Magic setter for MODULE fields
837                if (field.type == TemplateFieldType.MODULE && value && value.class == String) {
838                        value = AssayModule.findByName(value)
839                }
840
841                // Magic setter for LONG fields
842                if (field.type == TemplateFieldType.LONG && value && value.class == String) {
[1153]843                        // A check for invalidity is done in the validator of these fields. For that
844                        // reason, we just try to parse it here. If it fails, the validator will also
845                        // fail.
846                        try {
847                                value = Long.parseLong(value.trim())
848                        } catch( Exception e ) {}
[825]849                }
850
[406]851                // Set the field value
852                if (isDomainField(field)) {
[414]853                        // got a value?
854                        if (value) {
[1111]855                                //log.debug ".setting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") domain field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
[414]856                                this[field.name] = value
857                        } else {
[1111]858                                //log.debug ".unsetting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") domain field: [" + fieldName + "]"
[413]859
[497]860                                // remove value. For numbers, this is done by setting
[500]861                                // the value to 0, otherwise, setting it to NULL
862                                switch (field.type.toString()) {
[1153]863                                        case [ 'DOUBLE', 'RELTIME', 'LONG']:
[500]864                                                this[field.name] = 0;
865                                                break;
[559]866                                        case [ 'BOOLEAN' ]:
867                                                this[field.name] = false;
868                                                break;
[500]869                                        default:
870                                                this[field.name] = null
871                                }
[413]872                        }
873                } else {
[406]874                        // Caution: this assumes that all template...Field Maps are already initialized (as is done now above as [:])
875                        // If that is ever changed, the results are pretty much unpredictable (random Java object pointers?)!
[396]876                        def store = getStore(field.type)
[335]877
[561]878                        // If some value is entered (or 0 or BOOLEAN false), then save the value
[500]879                        // otherwise, it should not be present in the store, so
880                        // it is unset if it is.
[561]881                        if (value || value == 0 || ( field.type == TemplateFieldType.BOOLEAN && value == false)) {
[1111]882                                //log.debug ".setting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") template field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
[335]883
[500]884                                // set value
885                                store[fieldName] = value
886                        } else if (store[fieldName]) {
[1111]887                                //log.debug ".unsetting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") template field: [" + fieldName + "]"
[487]888
[500]889                                // remove the item from the Map (if present)
890                                store.remove(fieldName)
891                        }
[212]892                }
[406]893
[396]894                return this
[212]895        }
896
[413]897        /**
898         * Check if a given field is a domain field
[500]899         * @param TemplateField field instance
[413]900         * @return boolean
901         */
[406]902        boolean isDomainField(TemplateField field) {
[500]903                return isDomainField(field.name)
[406]904        }
905
[414]906        /**
907         * Check if a given field is a domain field
[500]908         * @param String field name
[414]909         * @return boolean
[453]910         */
[500]911        boolean isDomainField(String fieldName) {
912                return this.giveDomainFields()*.name.contains(fieldName)
913        }
914
[313]915        /**
[397]916         * Return all fields defined in the underlying template and the built-in
[500]917         * domain fields of this entity
[397]918         */
[902]919        def List<TemplateField> giveFields() {
[392]920                return this.giveDomainFields() + this.giveTemplateFields();
[224]921        }
922
[392]923        /**
[397]924         * Return all templated fields defined in the underlying template of this entity
925         */
[392]926        def List<TemplateField> giveTemplateFields() {
[435]927                return (this.template) ? this.template.fields : []
[384]928        }
929
[1224]930        def TemplateField getField( fieldName ) {
931                return getField(this.giveFields(), fieldName);
932        }
933
[313]934        /**
[662]935         * Look up the type of a certain template field
936         * @param String fieldName The name of the template field
937         * @return String The type (static member of TemplateFieldType) of the field, or null of the field does not exist
938         */
939        TemplateFieldType giveFieldType(String fieldName) {
940                def field = giveFields().find {
941                        it.name == fieldName
942                }
943                field?.type
944        }
945
946        /**
[506]947         * Return all relevant 'built-in' domain fields of the super class. Should be implemented by a static method
[392]948         * @return List with DomainTemplateFields
[500]949         * @see TemplateField
950         */
[392]951        abstract List<TemplateField> giveDomainFields()
952
[228]953        /**
[236]954         * Convenience method. Returns all unique templates used within a collection of TemplateEntities.
[540]955         *
956         * If the collection is empty, an empty set is returned. If none of the entities contains
957         * a template, also an empty set is returned.
[228]958         */
[572]959        static Collection<Template> giveTemplates(Collection<TemplateEntity> entityCollection) {
[847]960                def set = entityCollection*.template?.unique();
[536]961
[540]962                // If one or more entities does not have a template, the resulting
963                // set contains null. That is not what is meant.
[1036]964                set = set.findAll { it != null };
965
966                // Sort the list so we always have the same order
967                set = set.sort{ a, b ->
968                        a == null || b == null || a.equals(b) ? 0 :
969                        a.name < b.name ? -1 :
970                        a.name > b.name ?  1 :
971                        a.id < b.id ? -1 : 1
972                }
973
974                return set
[228]975        }
976
[236]977        /**
978         * Convenience method. Returns the template used within a collection of TemplateEntities.
979         * @throws NoSuchFieldException when 0 or multiple templates are used in the collection
980         * @return The template used by all members of a collection
981         */
[572]982        static Template giveSingleTemplate(Collection<TemplateEntity> entityCollection) {
[228]983                def templates = giveTemplates(entityCollection);
984                if (templates.size() == 0) {
985                        throw new NoSuchFieldException("No templates found in collection!")
[332]986                } else if (templates.size() == 1) {
[228]987                        return templates[0];
[332]988                } else {
[228]989                        throw new NoSuchFieldException("Multiple templates found in collection!")
990                }
991        }
[1257]992
993    /**
994     * Returns a Class object given by the entityname, but only if it is a subclass of TemplateEntity
995         *
996         * @return A class object of the given entity, null if the entity is not a subclass of TemplateEntity
997         * @throws ClassNotFoundException
998     */
999    static Class parseEntity( String entityName ) {
1000                if( entityName == null )
1001                        return null
1002
1003        // Find the templates
1004        def entity = Class.forName(entityName, true, Thread.currentThread().getContextClassLoader())
1005
1006        // succes, is entity an instance of TemplateEntity?
1007        if (entity?.superclass =~ /TemplateEntity$/ || entity?.superclass?.superclass =~ /TemplateEntity$/) {
1008            return entity;
1009        } else {
1010            return null;
1011        }
1012
1013    }
[901]1014}
Note: See TracBrowser for help on using the repository browser.