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

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

Implemented clone feature in template editor (ticket #6), added a rest call 'getUser' and added webflow to the application.properties again.

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