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

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