source: trunk/grails-app/domain/dbnp/studycapturing/TemplateField.groovy @ 1270

Last change on this file since 1270 was 1270, checked in by robert@…, 12 years ago

Bugfix in importing templates (ticket #76)

  • Property svn:keywords set to Author Date Rev
File size: 15.4 KB
Line 
1package dbnp.studycapturing
2
3import dbnp.data.Ontology
4
5/**
6 * A TemplateField is a specification for either a 'domain field' of a subclass of TemplateEntity or a
7 * 'template field' for a specific Template. See the Template class for an explanation of these terms.
8 * The TemplateField class contains all information which is needed to specify what kind of data can be stored
9 * in this particular field, such as the TemplateFieldType, the name, the ontologies from which terms can derive
10 * in case of an ONTOLOGYTERM field, the list entries in case of a STRINGLIST fields, and so on.
11 * The actual values of the template fields are stored in instances of subclasses of the TemplateEntity class.
12 * For example, if there exists a Study template with a 'description' TemplateField as a member of Template.fields,
13 * the actual description for each Study would be stored in the inherited templateStringFields map of that Study instance.
14 *
15 * One TemplateField can belong to many Templates, but they have to be the same entity as the TemplateField itself.
16 *
17 * Revision information:
18 * $Rev: 1270 $
19 * $Author: robert@isdat.nl $
20 * $Date: 2010-12-15 12:32:02 +0000 (wo, 15 dec 2010) $
21 */
22class TemplateField implements Serializable {
23
24        /** The name of the TemplateField, by which it is represented to the user.  */
25        String name
26
27        /** The type of this TemplateField, such as STRING, ONTOLOGYTERM etc. */
28        TemplateFieldType type
29
30        /** The entity for which this TemplateField is meant. Only Templates for this entity can contain this TemplateField */
31        Class entity
32
33        /** The unit of the values of this TemplateField (optional) */
34        String unit
35
36        /** The help string which is shown in the user interface to describe this template field (optional, TEXT) */
37        String comment
38
39        /** The different list entries for a STRINGLIST TemplateField. This property is only used if type == TemplateFieldType.STRINGLIST */
40        List listEntries
41
42        /** Indicates whether this field is required to be filled out or not */
43        boolean required
44
45        /** Indicates whether this field is the preferred identifier for the resulting templated entity.
46                This is for example used when importing to match entries in the database against the ones that are being imported. */
47        boolean preferredIdentifier
48
49        static hasMany = [
50                listEntries: TemplateFieldListItem,     // to store the entries to choose from when the type is 'item from predefined list'
51                ontologies: Ontology                            // to store the ontologies to choose from when the type is 'ontology term'
52        ]
53
54        static constraints = {
55                // outcommented for now due to bug in Grails / Hibernate
56                // see http://jira.codehaus.org/browse/GRAILS-6020
57                // This is to verify that TemplateField names are unique within templates of each super entity
58                // TODO: this probably has to change in the case of private templates of different users,
59                // which can co-exist with the same name. See also Template
60                // name(unique:['entity'])
61
62                name(nullable: false, blank: false)
63                type(nullable: false, blank: false)
64                entity(nullable: false, blank: false)
65                unit(nullable: true, blank: true)
66                comment(nullable: true, blank: true)
67                required(default: false)
68                preferredIdentifier(default: false)
69        }
70
71        static mapping = {
72                // Make sure the comments can be Strings of arbitrary length
73                comment type: 'text'
74                name column:"templatefieldname"
75                type column:"templatefieldtype"
76                entity column:"templatefieldentity"
77                unit column:"templatefieldunit"
78                comment column:"templatefieldcomment"
79        }
80
81        String toString() {
82                return name
83        }
84
85        /**
86         * return an escaped name which can be used in business logic
87         * @return String
88         */
89        def String escapedName() {
90                return name.toLowerCase().replaceAll("([^a-z0-9])", "_")
91        }
92
93        /**
94         * overloading the findAllByEntity method to make it function as expected
95         * @param Class entity (for example: dbnp.studycapturing.Subject)
96         * @return ArrayList
97         */
98        public static findAllByEntity(java.lang.Class entity) {
99                def results = []
100                // 'this' should not work in static context, so taking Template instead of this
101                TemplateField.findAll().each() {
102                        if (entity.equals(it.entity)) {
103                                results[results.size()] = it
104                        }
105                }
106
107                return results
108        }
109
110        /**
111         * Retrieves all list items of a stringlist template field that have been used in an object
112         *
113         * @return      ArrayList containing all list items of this template field that have been used in an object.
114         */
115        def getUsedListEntries() {
116                if( this.type != TemplateFieldType.STRINGLIST )
117                        return []
118
119                return this.listEntries.findAll { this.entryUsed( it ) }
120        }
121
122        /**
123         * Retrieves all list items of a stringlist template field that have never been used in an object
124         *
125         * @return      ArrayList containing all list items of this template field that have nevert been used in an object.
126         */
127        def getNonUsedListEntries() {
128                if( this.type != TemplateFieldType.STRINGLIST )
129                        return []
130               
131                return this.listEntries.findAll { !this.entryUsed( it ) }
132        }
133
134        /**
135         * Retrieves all ontologies of an ontologyterm template field that have been used in an object
136         *
137         * @return      ArrayList containing all ontologies of this template field that have been used in an object
138         *                      (i.e. all ontologies from which a term has been selected in this template field).
139         */
140        def getUsedOntologies() {
141                if( this.type != TemplateFieldType.ONTOLOGYTERM )
142                        return []
143
144                return this.ontologies.findAll { this.entryUsed( it ) }
145        }
146
147        /**
148         * Retrieves all list items of an ontologyterm template field that have never been used in an object
149         *
150         * @return      ArrayList containing all ontologies of this template field that have never been used in an object.
151         *                      (i.e. all ontologies from which no term has been selected in this template field).
152         */
153        def getNonUsedOntologies() {
154                if( this.type != TemplateFieldType.ONTOLOGYTERM )
155                        return []
156
157                return this.ontologies.findAll { !this.entryUsed( it ) }
158        }
159
160        /**
161         * Checks whether this template field is used in a template
162         *
163         * @returns             true iff this template field is used in a template (even if the template is never used), false otherwise
164         */
165        def inUse() {
166                return numUses() > 0;
167        }
168
169        /**
170         * The number of templates that use this template field
171         *
172         * @returns             the number of templates that use this template field.
173         */
174        def numUses() {
175                return getUses().size();
176        }
177
178        /**
179         * Retrieves the templates that use this template field
180         *
181         * @returns             a list of templates that use this template field.
182         */
183        def getUses() {
184                def templates = Template.findAll();
185                def elements;
186
187                if( templates && templates.size() > 0 ) {
188                        elements = templates.findAll { template -> template.fields.contains( this ) };
189                } else {
190                        return [];
191                }
192
193                return elements;
194        }
195
196        /**
197         * Checks whether this template field is used in a template and also filled in any instance of that template
198         *
199         * @returns             true iff this template field is used in a template, the template is instantiated
200         *                              and an instance has a value for this field. false otherwise
201         */
202        def isFilled() {
203                // Find all templates that use this template field
204                def templates = getUses();
205
206                if( templates.size() == 0 )
207                        return false;
208
209                // Find all entities that use these templates
210                def c = this.entity.createCriteria()
211                def entities = c {
212                        'in'("template",templates)
213                }
214
215                def filledEntities = entities.findAll { entity -> entity.getFieldValue( this.name ) }
216
217                return filledEntities.size() > 0;
218        }
219
220        /**
221         * Checks whether this template field is used in the given template and also filled in an instance of that template
222         *
223         * @returns             true iff this template field is used in the given template, the template is instantiated
224         *                              and an instance has a value for this field. false otherwise
225         */
226        def isFilledInTemplate(Template t) {
227                if( t == null ) 
228                        return false;
229                       
230                // If the template is not used, if can never be filled
231                if( !t.fields.contains( this ) )
232                        return false;
233
234                // Find all entities that use this template
235                def entities = entity.findAllByTemplate( t );
236                def filledEntities = entities.findAll { entity -> entity.getFieldValue( this.name ) }
237
238                return filledEntities.size() > 0;
239        }
240
241        /**
242         * Checks whether this template field is filled in all objects using a template with this template field
243         * If the template field is never used, the method returns true. If the template field is used in a template,
244         * but no objects with this template exist, the method also returns true
245         *
246         * @returns             false iff objects exist using this template field, but without a value for this field. true otherwise
247         */
248        def isFilledInAllObjects() {
249                // Find all templates that use this entity
250                def templates = getUses();
251
252                if( templates.size() == 0 )
253                        return true;
254
255                // Find all entities that use these templates
256                def c = this.entity.createCriteria()
257                def entities = c {
258                        'in'("template",templates)
259                }
260
261                if( entities.size() == 0 )
262                        return true;
263
264                def emptyEntities = entities.findAll { entity -> !entity.getFieldValue( this.name ) }
265
266                return ( emptyEntities.size() == 0 );
267        }
268
269        /**
270         * Check whether a templatefield that is used in a template may still be edited or deleted.
271         * That is possible if the templatefield is never filled and the template is only used in one template
272         *
273         * This method should only be used for templatefields used in a template that is currently shown. Otherwise
274         * the user may edit this template field, while it is also in use in another template than is currently shown.
275         * That lead to confusion.
276         *
277         * @returns true iff this template may still be edited or deleted.
278         */
279        def isEditable() {
280                return !isFilled() && numUses() == 1;
281        }
282
283        /**
284         * Checks whether the given list item is selected in an entity where this template field is used
285         *
286         * @param       item    ListItem to check.
287         * @returns                     true iff the list item is part of this template field and the given list
288         *                                      item is selected in an entity where this template field is used. false otherwise
289         *                                      Returns false if the type of this template field is other than STRINGLIST
290         */
291        def entryUsed(TemplateFieldListItem item) {
292                if( this.type != TemplateFieldType.STRINGLIST )
293                        return false;
294
295                // Find all templates that use this template field
296                def templates = getUses();
297
298                if( templates.size() == 0 )
299                        return false;
300
301                // Find all entities that use these templates
302                def c = this.entity.createCriteria()
303                def entities = c {
304                        'in'("template",templates)
305                }
306
307                if( entities.size() == 0 ) 
308                        return false
309                       
310                def entitiesWithListItem = entities.findAll { entity -> entity.getFieldValue( this.name ).equals( item ) }
311
312                return entitiesWithListItem.size() > 0;
313        }
314
315        /**
316         * Checks whether a term from the given ontology is selected in an entity where this template field is used
317         *
318         * @param       item    ListItem to check.
319         * @returns                     true iff the ontology is part of this template field and a term from the given
320         *                                      ontology is selected in an entity where this template field is used. false otherwise
321         *                                      Returns false if the type of this template field is other than ONTOLOGYTERM
322         */
323        def entryUsed(Ontology item) {
324                if( this.type != TemplateFieldType.ONTOLOGYTERM )
325                        return false;
326
327                // Find all templates that use this template field
328                def templates = getUses();
329
330                // If the template field is never used in a template, it will also never
331                // be filled, and this Ontology will never be used
332                if( templates.size() == 0 )
333                        return false;
334
335                // Find all entities that use these templates
336                def c = this.entity.createCriteria()
337                def entities = c {
338                        'in'("template",templates)
339                }
340
341                if( entities.size() == 0 )
342                        return false
343
344                def entitiesWithOntology = entities.findAll { entity -> 
345                        def value = entity.getFieldValue( this.name );
346                        if( value ) 
347                                return value.ontology.equals( item )
348                        else
349                                return false;
350                }
351
352                return entitiesWithOntology.size() > 0;
353        }
354
355        /**
356         * Checks whether this field is filled in any of the entities in the given list
357         *
358         * @param       List    List of TemplateEntities to search in
359         * @return      boolean True iff any of the given entities has this field as template field, and has a value for it. False otherwise
360         */
361        def isFilledInList( entityList ) {
362                if( !entityList )
363                        return false;
364                       
365                return true in entityList.collect { it.fieldExists( this.name ) && it.getFieldValue( this.name ) != null }?.flatten()
366        }
367
368
369        /**
370         * Check whether the contents of the other templatefield and the current templatefield are equal.
371         * For this check the comments field doesn't matter.
372         *
373         * @return      true iff this template field equals the other template field
374         *                      (the comments field may be different)
375         */
376        public boolean contentEquals( Object otherObject ) {
377                if( !( otherObject instanceof TemplateField ) )
378                        return false
379
380                TemplateField otherField = (TemplateField) otherObject;
381
382                if( otherField == this )
383                        return true
384
385                if( otherField == null )
386                        return false
387
388                if( otherField.entity != this.entity ) {
389                        return false
390                }
391                if( otherField.name != this.name ) {
392                        return false
393                }
394                if( otherField.type != this.type ) {
395                        return false
396                }
397                if( otherField.unit != this.unit ) {
398                        return false
399                }
400                if( otherField.required != this.required ) {
401                        return false
402                }
403
404                if( otherField.preferredIdentifier != this.preferredIdentifier ) {
405                        return false
406                }
407
408                // Check whether the list entries are equal (except for the order)
409                def size1 = otherField.listEntries?.size() ?: 0
410                def size2 = this.listEntries?.size() ?: 0
411                if( size1 != size2 ) {
412                        return false
413                }
414               
415                if( otherField.listEntries != null && this.listEntries != null ) {
416                        for( def entry in this.listEntries ) {
417                                def entryFound = false;
418                                for( def otherEntry in otherField.listEntries ) {
419                                        if( otherEntry.name == entry.name ) {
420                                                entryFound = true;
421                                                break
422                                        }
423                                }
424
425                                if( !entryFound ) {
426                                        return false
427                                }
428                        }
429                }
430               
431                // Check whether the ontologies are equal (except for the order)
432                size1 = otherField.ontologies?.size() ?: 0
433                size2 = this.ontologies?.size() ?: 0
434                if( size1 != size2 ) {
435                        return false
436                }
437                if( this.ontologies != null && otherField.ontologies != null ) {
438                        for( def ontology in this.ontologies ) {
439                                if( !otherField.ontologies.contains( ontology ) ) {
440                                        return false
441                                }
442                        }
443                }
444               
445                // If all tests pass, the objects are content-equal
446                return true
447        }
448
449        /**
450         * Create a new template field based on the parsed XML object.
451         *
452         * @see grails.converters.XML#parse(java.lang.String)
453         * @throws IllegalArgumentException
454         */
455        public static parse(Object xmlObject, Class entity) {
456                def t = new TemplateField();
457
458                t.name = xmlObject?.name?.text()
459                t.unit = xmlObject?.unit?.text() == "" ? null : xmlObject?.unit?.text()
460                t.comment = xmlObject?.comment?.text()
461                t.required = xmlObject?.required?.text() == 'true' ? true : false
462                t.preferredIdentifier = xmlObject?.preferredIdentifier?.text() == 'true' ? true : false
463
464                t.entity = entity
465
466                t.type = TemplateFieldType.valueOf( xmlObject?.type?.text() )
467
468                // Search for ontologies
469                xmlObject.ontologies?.ontology.each {
470                        def ncboId = it.ncboId?.text();
471                        t.addToOntologies( Ontology.getOrCreateOntologyByNcboId( ncboId ) );
472                }
473
474                // Search for list entries
475                xmlObject.listItems?.listItem.each {
476                        def name = ""
477                        if( it.name )
478                                name = it.name.text()
479
480                        t.addToListEntries( new TemplateFieldListItem( name: name ) );
481                }
482                return t;
483        }
484
485
486}
Note: See TracBrowser for help on using the repository browser.