root/trunk/grails-app/domain/dbnp/studycapturing/TemplateEntity.groovy @ 1257

Revision 1257, 25.8 KB (checked in by robert@…, 3 years ago)

Added an export and import functionality for templates, as described in ticket #76

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