source: trunk/grails-app/controllers/dbnp/studycapturing/TemplateEditorController.groovy @ 558

Last change on this file since 558 was 558, checked in by roberth, 11 years ago

Improved template editor , stringlists and ontologyfields can be edited and extra checks are built in.

  • Property svn:keywords set to Date Author Rev
File size: 22.2 KB
Line 
1/**
2 * TemplateEditorController Controler
3 *
4 * Webflow driven template editor
5 *
6 * @author  Jeroen Wesbeek
7 * @since       20100415
8 * @package     studycapturing
9 *
10 * Revision information:
11 * $Rev: 558 $
12 * $Author: roberth $
13 * $Date: 2010-06-11 14:00:10 +0000 (vr, 11 jun 2010) $
14 */
15package dbnp.studycapturing
16import dbnp.data.*
17import dbnp.studycapturing.*
18import cr.co.arquetipos.crypto.Blowfish
19import grails.converters.*
20import java.lang.reflect.*;
21
22class TemplateEditorController {
23    def entityName;
24    def entity;
25
26    /**
27     * index closure
28     */
29    def index = {
30        // Check whether a right entity is given
31        if( !_checkEntity() ) {
32                        return
33                }
34
35        // fetch all templates for this entity
36        def templates = Template.findAllByEntity(entity)
37
38                // Generate a human readable entity name
39                def parts = entityName.tokenize( '.' );
40                def humanReadableEntity = parts[ parts.size() - 1 ];
41
42        return [
43            entity: entity,
44            templates: templates,
45            encryptedEntity: params.entity,
46            humanReadableEntity: humanReadableEntity,
47        ];
48    }
49
50        /**
51         * Shows the editing of a template
52         */
53        def template = {
54        // Check whether a right entity is given
55        if( !_checkEntity() ) {
56                        return
57                }
58
59        // Check whether a template is selected. If not, redirect the user to the index
60        def selectedTemplate = params.template;
61        def template = null;
62                def domainFields = null;
63
64        if( selectedTemplate ) {
65            template = Template.get( selectedTemplate );
66
67                        // Find the domain classes for this template/entity
68                        // See http://www.javaworld.com/javaworld/javaqa/1999-07/06-qa-invoke.html
69                        Method m = template.entity.getDeclaredMethod("giveDomainFields", null);
70                        domainFields = m.invoke( null, null );
71        } else {
72                        redirect(action:"index",params:[entity:params.entity])
73                        return;
74                }
75
76        // fetch all templates for this entity
77        def templates = Template.findAllByEntity(entity)
78
79                // Generate a human readable entity name
80                def parts = entityName.tokenize( '.' );
81                def humanReadableEntity = parts[ parts.size() - 1 ];
82
83
84
85                // Find all available fields
86                def allFields = TemplateField.findAllByEntity( entity ).sort { a, b -> a.name <=> b.name }
87
88        return [
89            entity: entity,
90            templates: templates,
91            encryptedEntity: params.entity,
92            fieldTypes: TemplateFieldType.list(),
93                        ontologies: Ontology.list(),
94            humanReadableEntity: humanReadableEntity,
95
96            template: template,
97                        allFields: allFields,
98                        domainFields: domainFields
99        ];
100
101        }
102
103
104    /**
105     * Shows an error page
106     *
107     * TODO: improve the error page
108     */
109    def error = {
110        render( 'view': 'error' );
111    }
112
113    /**
114     * Creates a new template using a AJAX call
115         *
116         * @return                      JSON object with two entries:
117         *                                              id: [id of this object]
118         *                                              html: HTML to replace the contents of the LI-item that was updated.
119         *                                      On error the method gives a HTTP response status 500 and the error
120     */
121    def createTemplate = {
122                // Decode the entity
123        if( !_checkEntity() ) {
124                        response.status = 500;
125                        render "Incorrect entity given";
126                        return;
127                }
128
129                params.entity = entity;
130
131                // Create the template field and add it to the template
132                def template = new Template( params );
133        if (template.save(flush: true)) {
134
135                        def html = g.render( template: 'elements/liTemplate', model: [template: template] );
136                        def output = [ id: template.id, html: html ];
137                        render output as JSON;
138
139            //render '';
140        } else {
141            response.status = 500;
142            render 'Template could not be created because errors occurred.';
143            return
144        }
145    }
146
147    /**
148     * Updates a selected template using a AJAX call
149         *
150         * @param id    ID of the template to update
151         * @return              JSON object with two entries:
152         *                                      id: [id of this object]
153         *                                      html: HTML to replace the contents of the LI-item that was updated.
154         *                              On error the method gives a HTTP response status 500 and the error
155     */
156    def updateTemplate = {
157        // Search for the template field
158        def template = Template.get( params.id );
159        if( !template ) {
160            response.status = 404;
161            render 'Template not found';
162            return;
163        }
164
165        // Update the field if it is not updated in between
166        if (params.version) {
167            def version = params.version.toLong()
168            if (template.version > version) {
169                response.status = 500;
170                render 'Template was updated while you were working on it. Please reload and try again.';
171                return
172            }
173        }
174
175        template.properties = params
176        if (!template.hasErrors() && template.save(flush: true)) {
177                        def html = g.render( template: 'elements/liTemplate', model: [template: template] );
178                        def output = [ id: template.id, html: html ];
179                        render output as JSON;
180        } else {
181            response.status = 500;
182            render 'Template was not updated because errors occurred.';
183            return
184        }
185    }
186
187    /**
188     * Deletes a template using a AJAX call
189     *
190         * @param template              ID of the template to move
191         * @return                              JSON object with one entry:
192         *                                                      id: [id of this object]
193         *                                              On error the method gives a HTTP response status 500 and the error
194     */
195    def deleteTemplate = {
196        // Search for the template field
197        def  template = Template.get( params.template );
198        if( !template ) {
199            response.status = 404;
200            render 'Template not found';
201            return;
202        }
203
204        // Delete the template field
205                try {
206                        template.delete(flush: true)
207
208                        def output = [ id: template.id ];
209                        render output as JSON;
210                }
211                catch (org.springframework.dao.DataIntegrityViolationException e) {
212            response.status = 500;
213            render 'Template could not be deleted: ' + e.getMessage();
214                }
215    }
216
217    /**
218     * Creates a new template field using a AJAX call
219         *
220         * @param template      ID of the template to add a field to
221         * @return                      JSON object with two entries:
222         *                                              id: [id of this object]
223         *                                              html: HTML to replace the contents of the LI-item that was updated.
224         *                                      On error the method gives a HTTP response status 500 and the error
225     */
226    def createField = {
227        // Search for the template
228        def template = Template.get( params.template );
229
230        if( !template ) {
231            response.status = 404;
232            render 'Template not found';
233            return;
234        }
235
236                // Decode the entity, in order to set a good property
237        if( !_checkEntity() ) {
238                        response.status = 500;
239                        render "Incorrect entity given";
240                        return;
241                }
242
243                params.entity = entity;
244
245                // See whether this field already exists. It is checked by name, type and unit and entity
246                // The search is done using search by example (see http://grails.org/DomainClass+Dynamic+Methods, method find)
247                def uniqueParams = [ name: params.name, type: params.type, unit: params.unit, entity: params.entity ];
248                if( TemplateField.find( new TemplateField( uniqueParams ) ) ) {
249                        response.status = 500;
250                        render "A field with this name, type and unit already exists.";
251                        return;
252                }
253
254                // See whether this exists as domain field. If it does, raise an error
255                Method m = template.entity.getDeclaredMethod("giveDomainFields", null);
256                def domainFields = m.invoke( null, null );
257                if( domainFields.find { it.name.toLowerCase() == params.name.toLowerCase() } ) {
258                        response.status = 500;
259                        render "All templates for entity " + template.entity + " contain a domain field with name " + params.name + ". You can not create a field with this name.";;
260                        return;
261                }
262
263                // If this field is type stringlist, we have to prepare the parameters
264                if( params.type.toString() == 'STRINGLIST' ) {
265                        def listEntries = [];
266                        params.listEntries.eachLine {
267                                // Search whether a listitem with this name already exists.
268                                // if it does, use that one to prevent pollution of the database
269                                def name = it.trim();
270                                def listitem = TemplateFieldListItem.findByName( name );7
271                                if( !listitem ) {
272                                        listitem = new TemplateFieldListItem( name: name )
273                                }
274                                listEntries.add( listitem );
275                        }
276
277                        params.listEntries = listEntries;
278                } else {
279                        params.remove( 'listEntries' );
280                }
281
282                // If this field isnot a ontologyterm, we should remove the ontologies
283                if( params.type.toString() != 'ONTOLOGYTERM' ) {
284                        params.remove( 'ontologies' );
285                }
286
287                // Create the template field and add it to the template
288                def templateField = new TemplateField( params );
289        if (templateField.save(flush: true)) {
290
291                        def html = g.render( template: 'elements/available', model: [templateField: templateField, ontologies: Ontology.list(), fieldTypes: TemplateFieldType.list()] );
292                        def output = [ id: templateField.id, html: html ];
293                        render output as JSON;
294
295            //render '';
296        } else {
297            response.status = 500;
298            render 'TemplateField could not be created because errors occurred.';
299            return
300        }
301    }
302
303    /**
304     * Updates a selected template field using a AJAX call
305         *
306         * @param id    ID of the field to update
307         * @return              JSON object with two entries:
308         *                                      id: [id of this object]
309         *                                      html: HTML to replace the contents of the LI-item that was updated.
310         *                              On error the method gives a HTTP response status 500 and the error
311     */
312    def updateField = {
313        // Search for the template field
314        def templateField = TemplateField.get( params.id );
315        if( !templateField ) {
316            response.status = 404;
317            render 'TemplateField not found';
318            return;
319        }
320
321        // Update the field if it is not updated in between
322        if (params.version) {
323            def version = params.version.toLong()
324            if (templateField.version > version) {
325                response.status = 500;
326                render 'TemplateField was updated while you were working on it. Please reload and try again.';
327                return
328            }
329        }
330
331                // If this field is type stringlist or ontology, we have to prepare the parameters
332                if( params.type.toString() == 'STRINGLIST' ) {
333                        def listEntries = [];
334                        params.listEntries.eachLine {
335                                // Search whether a listitem with this name already exists.
336                                // if it does, use that one to prevent pollution of the database
337                                def name = it.trim();
338                                def listitem = TemplateFieldListItem.findByName( name );7
339                                if( !listitem ) {
340                                        listitem = new TemplateFieldListItem( name: name )
341                                }
342                                listEntries.add( listitem );
343                        }
344
345                        params.listEntries = listEntries;
346                } else {
347                        params.remove( 'listEntries' );
348                }
349
350                // If this field is a ontologyterm, we add ontology objects
351                if( params.type.toString() == 'ONTOLOGYTERM' && params.ontologies ) {
352                        params.ontologies = Ontology.getAll( params.ontologies.collect { Integer.parseInt( it ) } );
353                } else {
354                        params.remove( 'ontologies' );
355                }
356
357                // Set all parameters
358                templateField.properties = params
359        if (!templateField.hasErrors() && templateField.save(flush: true)) {
360                        def html = g.render( template: 'elements/available', model: [templateField: templateField, ontologies: Ontology.list(), fieldTypes: TemplateFieldType.list()] );
361                        def output = [ id: templateField.id, html: html ];
362                        render output as JSON;
363        } else {
364            response.status = 500;
365            render 'TemplateField was not updated because errors occurred.';
366            return
367        }
368    }
369
370    /**
371     * Deletes a template field using a AJAX call
372     *
373         * @param templateField ID of the templatefield to move
374         * @return                              JSON object with one entry:
375         *                                                      id: [id of this object]
376         *                                              On error the method gives a HTTP response status 500 and the error
377     */
378    def deleteField = {
379        // Search for the template field
380        def  templateField = TemplateField.get( params.templateField );
381        if( !templateField ) {
382            response.status = 404;
383            render 'TemplateField not found';
384            return;
385        }
386
387        // Delete the template field
388                try {
389                        templateField.delete(flush: true)
390
391                        def output = [ id: templateField.id ];
392                        render output as JSON;
393                }
394                catch (org.springframework.dao.DataIntegrityViolationException e) {
395            response.status = 500;
396            render 'TemplateField could not be deleted: ' + e.getMessage();
397                }
398    }
399
400    /**
401     * Adds a new template field to a template using a AJAX call
402         *
403         * @param template      ID of the template to add a field to
404         * @return                      JSON object with two entries:
405         *                                              id: [id of this object]
406         *                                              html: HTML to replace the contents of the LI-item that was updated.
407         *                                      On error the method gives a HTTP response status 404 or 500 and the error
408     */
409    def addField = {
410        // Search for the template
411        def template = Template.get( params.template );
412
413        if( !template ) {
414            response.status = 404;
415            render 'Template not found';
416            return;
417        }
418
419        // Search for the template field
420        def templateField = TemplateField.get( params.templateField );
421        if( !templateField ) {
422            response.status = 404;
423            render 'TemplateField does not exist';
424            return;
425        }
426
427        // The template field should exist within the template
428        if( template.fields.contains( templateField ) ) {
429            response.status = 500;
430            render 'TemplateField is already found within template';
431            return;
432        }
433
434                // If the template is in use, only non-required fields can be added
435                if( template.inUse() && templateField.required ) {
436                        response.status = 500;
437                        render 'Only non-required fields can be added to templates that are in use.'
438                        return;
439                }
440
441                // All field names within a template should be unique
442                if( template.fields.find { it.name.toLowerCase() == templateField.name.toLowerCase() } ) {
443                        response.status = 500;
444                        render 'This template already contains a field with name ' + templateField.name + '. Field names should be unique within a template.'
445                        return;
446                }
447
448                if( !params.position || Integer.parseInt( params.position ) == -1) {
449                        template.fields.add( templateField )
450                } else {
451                        template.fields.add( Integer.parseInt( params.position ), templateField )
452                }
453                template.save(flush:true);
454
455                def html = g.render( template: 'elements/selected', model: [templateField: templateField, template: template, ontologies: Ontology.list(), fieldTypes: TemplateFieldType.list()] );
456                def output = [ id: templateField.id, html: html ];
457                render output as JSON;
458    }
459
460    /**
461     * Removes a selected template field from the template using a AJAX call
462         *
463         * @param templateField ID of the field to update
464         * @param template              ID of the template for which the field should be removed
465         * @return                              JSON object with two entries:
466         *                                                      id: [id of this object]
467         *                                                      html: HTML to replace the contents of the LI-item that was updated.
468         *                                              On error the method gives a HTTP response status 404 or 500 and the error
469     */
470    def removeField = {
471        // Search for the template
472        def template = Template.get( params.template );
473
474        if( !template ) {
475            response.status = 404;
476            render 'Template not found';
477            return;
478        }
479
480        // Search for the template field
481        def templateField = TemplateField.get( params.templateField );
482        if( !templateField ) {
483            response.status = 404;
484            render 'TemplateField not found';
485            return;
486        }
487
488        // The template field should exist within the template
489        if( !template.fields.contains( templateField ) ) {
490            response.status = 404;
491            render 'TemplateField not found within template';
492            return;
493        }
494
495                // If the template is in use, field can not be removed
496                if( template.inUse() ) {
497                        response.status = 500;
498                        render 'No fields can be removed from templates that are in use.'
499                        return;
500                }
501
502                // Delete the field from this template
503        def currentIndex = template.fields.indexOf( templateField );
504        template.fields.remove( currentIndex );
505                template.save(flush:true);
506
507
508                def html = g.render( template: 'elements/available', model: [templateField: templateField, ontologies: Ontology.list(), fieldTypes: TemplateFieldType.list()] );
509                def output = [ id: templateField.id, html: html ];
510                render output as JSON;
511    }
512
513    /**
514     * Moves a template field using a AJAX call
515     *
516         * @param template              ID of the template that contains this field
517         * @param templateField ID of the templatefield to move
518         * @param position              New index of the templatefield in the array. The index is 0-based.
519         * @return                              JSON object with two entries:
520         *                                                      id: [id of this object]
521         *                                                      html: HTML to replace the contents of the LI-item that was updated.
522         *                                              On error the method gives a HTTP response status 500 and the error
523     */
524    def moveField = {
525        // Search for the template
526        def template = Template.get( params.template );
527
528        if( !template ) {
529            response.status = 404;
530            render 'Template not found';
531            return;
532        }
533
534        // Search for the template field
535        def  templateField = TemplateField.get( params.templateField );
536        if( !templateField ) {
537            response.status = 404;
538            render 'TemplateField not found';
539            return;
540        }
541
542        // The template field should exist within the template
543        if( !template.fields.contains( templateField ) ) {
544            response.status = 404;
545            render 'TemplateField not found within template';
546            return;
547        }
548
549        // Move the item
550        def currentIndex = template.fields.indexOf( templateField );
551        def moveField = template.fields.remove( currentIndex );
552        template.fields.add( Integer.parseInt( params.position ), moveField );
553                template.save(flush:true);
554
555                def html = g.render( template: 'elements/selected', model: [templateField: templateField, template: template, fieldTypes: TemplateFieldType.list()] );
556                def output = [ id: templateField.id, html: html ];
557                render output as JSON;
558    }
559
560        /**
561         * Checks how many template use a specific template field
562         *
563         * @param       id      ID of the template field
564         * @return      int     Number of uses
565         */
566        def numFieldUses = {
567        // Search for the template field
568        def  templateField = TemplateField.get( params.id );
569        if( !templateField ) {
570            response.status = 404;
571            render 'TemplateField not found';
572            return;
573        }
574
575                render templateField.numUses();
576        }
577
578        /**
579         * Adds a ontolgy based on the ID given
580         *
581         * @param       ncboID
582         * @return      JSON    Ontology object
583         */
584        def addOntologyById = {
585                def id = params.ncboID;
586
587                if( !id ) {
588                        response.status = 500;
589                        render 'No ID given'
590                        return;
591                }
592
593                if( Ontology.findByNcboId( Integer.parseInt(  id ) ) ) {
594                        response.status = 500;
595                        render 'This ontology was already added to the system';
596                        return;
597                }
598
599                def ontology = null;
600
601                try {
602                        ontology = dbnp.data.Ontology.getBioPortalOntology( id );
603                } catch( Exception e ) {
604                        response.status = 500;
605                        render e.getMessage();
606                        return;
607                }
608
609                if( !ontology ) {
610                        response.status = 404;
611                        render 'Ontology with ID ' + id + ' not found';
612                        return;
613                }
614
615                // Validate ontology
616                if (!ontology.validate()) {
617                        response.status = 500;
618                        render ontology.errors.join( '; ' );
619                        return;
620                }
621
622                // Save ontology and render the object as JSON
623                ontology.save(flush: true)
624                render ontology as JSON
625        }
626
627        /**
628         * Adds a ontolgy based on the ID given
629         *
630         * @param       ncboID
631         * @return      JSON    Ontology object
632         */
633        def addOntologyByTerm = {
634                def id = params.termID;
635
636                if( !id ) {
637                        response.status = 500;
638                        render 'No ID given'
639                        return;
640                }
641
642                def ontology = null;
643
644                try {
645                        ontology = dbnp.data.Ontology.getBioPortalOntologyByTerm( id );
646                } catch( Exception e ) {
647                        response.status = 500;
648                        render e.getMessage();
649                        return;
650                }
651
652                if( !ontology ) {
653                        response.status = 404;
654                        render 'Ontology form term ' + id + ' not found';
655                        return;
656                }
657
658                // Validate ontology
659                if (!ontology.validate()) {
660                        response.status = 500;
661                        render ontology.errors.join( '; ' );
662                        return;
663                }
664
665                // Save ontology and render the object as JSON
666                ontology.save(flush: true)
667                render ontology as JSON
668        }
669
670
671    /**
672     * Checks whether a correct entity is given
673         *
674         * @return      boolean True if a correct entity is given. Returns false and raises an error otherwise
675         * @see         error()
676     */
677    def _checkEntity = {
678        // got a entity get parameter?
679        entityName = _parseEntityType();
680
681        if( !entityName ) {
682            error();
683            return false;
684        }
685
686        // Create an object of this type
687        entity = _getEntity( entityName );
688
689        if( !entity ) {
690            error();
691            return false
692        }
693
694        return true;
695    }
696
697
698    /**
699     * Checks whether the entity type is given and can be parsed
700         *
701         * @return      Name of the entity if parsing is succesful, false otherwise
702     */
703    def _parseEntityType() {
704        def entityName;
705        if (params.entity) {
706            // decode entity get parameter
707            if (grailsApplication.config.crypto) {
708                    // generate a Blowfish encrypted and Base64 encoded string.
709                    entityName = Blowfish.decryptBase64(
710                            params.entity,
711                            grailsApplication.config.crypto.shared.secret
712                    )
713            } else {
714                    // base64 only; this is INSECURE! Even though it is not
715                    // very likely, it is possible to exploit this and have
716                    // Grails dynamically instantiate whatever class you like.
717                    // If that constructor does something harmfull this could
718                    // be dangerous. Hence, use encryption (above) instead...
719                    entityName = new String(params.entity.toString().decodeBase64())
720            }
721
722            return entityName;
723        } else {
724            return false;
725        }
726    }
727
728    /**
729     * Creates an object of the given entity.
730         *
731         * @return False if the entity is not a subclass of TemplateEntity
732     */
733    def _getEntity( entityName ) {
734        // Find the templates
735        def entity = Class.forName(entityName, true, this.getClass().getClassLoader())
736
737        // succes, is entity an instance of TemplateEntity?
738        if (entity.superclass =~ /TemplateEntity$/) {
739            return entity;
740        } else {
741            return false;
742        }
743
744    }
745}
Note: See TracBrowser for help on using the repository browser.