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

Last change on this file since 897 was 897, checked in by keesvb, 10 years ago

fixed multiple bugs in importer, really implemented the header and start row functionality, updated Ontology setter with proper error message

  • Property svn:keywords set to Author Rev Date
File size: 24.8 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: 897 $
16 * $Author: keesvb $
17 * $Date: 2010-09-21 08:58:35 +0000 (di, 21 sep 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][8] + '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                                // TODO: search ontology for the term online (it may still exist) and insert it into the Term cache
628                                // if not found, throw exception
629                                else {
630                                        throw new IllegalArgumentException("Ontology term not recognized (not in the GSCF ontology cache): ${value} when setting field ${fieldName}")
631                                }
632                        }
633                }
634
635                // Magic setter for TEMPLATE fields
636                if (field.type == TemplateFieldType.TEMPLATE && value && value.class == String) {
637                        value = Template.findByName(value)
638                }
639
640                // Magic setter for MODULE fields
641                if (field.type == TemplateFieldType.MODULE && value && value.class == String) {
642                        value = AssayModule.findByName(value)
643                }
644
645                // Magic setter for LONG fields
646                if (field.type == TemplateFieldType.LONG && value && value.class == String) {
647                        // TODO, check for invalids?
648                        value = Long.parseLong(value.trim())
649                }
650
651                // Set the field value
652                if (isDomainField(field)) {
653                        // got a value?
654                        if (value) {
655                                println ".setting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") domain field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
656                                this[field.name] = value
657                        } else {
658                                println ".unsetting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") domain field: [" + fieldName + "]"
659
660                                // remove value. For numbers, this is done by setting
661                                // the value to 0, otherwise, setting it to NULL
662                                switch (field.type.toString()) {
663                                        case ['INTEGER', 'FLOAT', 'DOUBLE', 'RELTIME', 'LONG']:
664                                                this[field.name] = 0;
665                                                break;
666                                        case [ 'BOOLEAN' ]:
667                                                this[field.name] = false;
668                                                break;
669                                        default:
670                                                this[field.name] = null
671                                }
672                        }
673                } else {
674                        // Caution: this assumes that all template...Field Maps are already initialized (as is done now above as [:])
675                        // If that is ever changed, the results are pretty much unpredictable (random Java object pointers?)!
676                        def store = getStore(field.type)
677
678                        // If some value is entered (or 0 or BOOLEAN false), then save the value
679                        // otherwise, it should not be present in the store, so
680                        // it is unset if it is.
681                        if (value || value == 0 || ( field.type == TemplateFieldType.BOOLEAN && value == false)) {
682                                println ".setting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") template field: [" + fieldName + "] ([" + value.toString() + "] of type [" + value.class + "])"
683
684                                // set value
685                                store[fieldName] = value
686                        } else if (store[fieldName]) {
687                                println ".unsetting [" + ((super) ? super.class : '??') + "] ("+getIdentifier()+") template field: [" + fieldName + "]"
688
689                                // remove the item from the Map (if present)
690                                store.remove(fieldName)
691                        }
692                }
693
694                return this
695        }
696
697        /**
698         * Check if a given field is a domain field
699         * @param TemplateField field instance
700         * @return boolean
701         */
702        boolean isDomainField(TemplateField field) {
703                return isDomainField(field.name)
704        }
705
706        /**
707         * Check if a given field is a domain field
708         * @param String field name
709         * @return boolean
710         */
711        boolean isDomainField(String fieldName) {
712                return this.giveDomainFields()*.name.contains(fieldName)
713        }
714
715        /**
716         * Return all fields defined in the underlying template and the built-in
717         * domain fields of this entity
718         */
719        def List<TemplateField> giveFields() {
720                return this.giveDomainFields() + this.giveTemplateFields();
721        }
722
723        /**
724         * Return all templated fields defined in the underlying template of this entity
725         */
726        def List<TemplateField> giveTemplateFields() {
727                return (this.template) ? this.template.fields : []
728        }
729
730        /**
731         * Look up the type of a certain template field
732         * @param String fieldName The name of the template field
733         * @return String The type (static member of TemplateFieldType) of the field, or null of the field does not exist
734         */
735        TemplateFieldType giveFieldType(String fieldName) {
736                def field = giveFields().find {
737                        it.name == fieldName
738                }
739                field?.type
740        }
741
742        /**
743         * Return all relevant 'built-in' domain fields of the super class. Should be implemented by a static method
744         * @return List with DomainTemplateFields
745         * @see TemplateField
746         */
747        abstract List<TemplateField> giveDomainFields()
748
749        /**
750         * Convenience method. Returns all unique templates used within a collection of TemplateEntities.
751         *
752         * If the collection is empty, an empty set is returned. If none of the entities contains
753         * a template, also an empty set is returned.
754         */
755        static Collection<Template> giveTemplates(Collection<TemplateEntity> entityCollection) {
756                def set = entityCollection*.template?.unique();
757
758                // If one or more entities does not have a template, the resulting
759                // set contains null. That is not what is meant.
760                return set.findAll { it != null };
761        }
762
763        /**
764         * Convenience method. Returns the template used within a collection of TemplateEntities.
765         * @throws NoSuchFieldException when 0 or multiple templates are used in the collection
766         * @return The template used by all members of a collection
767         */
768        static Template giveSingleTemplate(Collection<TemplateEntity> entityCollection) {
769                def templates = giveTemplates(entityCollection);
770                if (templates.size() == 0) {
771                        throw new NoSuchFieldException("No templates found in collection!")
772                } else if (templates.size() == 1) {
773                        return templates[0];
774                } else {
775                        throw new NoSuchFieldException("Multiple templates found in collection!")
776                }
777        }
778}
Note: See TracBrowser for help on using the repository browser.