Changeset 1257


Ignore:
Timestamp:
Dec 9, 2010, 9:12:10 PM (6 years ago)
Author:
robert@…
Message:

Added an export and import functionality for templates, as described in ticket #76

Location:
trunk
Files:
9 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/dbnp/studycapturing/TemplateController.groovy

    r959 r1257  
    1515package dbnp.studycapturing
    1616
     17// Automatic marshalling of XML and JSON
     18import grails.converters.*
     19import grails.plugins.springsecurity.Secured
     20import dbnp.authentication.AuthenticationService
     21
     22@Secured(['IS_AUTHENTICATED_REMEMBERED'])
    1723class TemplateController {
    18         def scaffold = Template
     24        def authenticationService
     25
     26        /**
     27         * Shows a form to pick a file to import templates
     28         */
     29        def importTemplate = {
     30        }
     31
     32        /**
     33         * Handles file import
     34         */
     35        def handleImportedFile = {
     36                if( !request.getFile("file") ) {
     37                        flash.message = "No file given.";
     38                        redirect( action: 'importTemplate' );
     39                        return;
     40                }
     41
     42                // Parse XML
     43                def xml
     44               
     45                try {
     46                        xml = XML.parse( request.getFile("file").inputStream.text )
     47                } catch( Exception e ) {
     48                        // Error in parsing. Probably the file is not a XML file
     49                        flash.message = "Imported file could not be read. Please specify an XML template file.";
     50                        redirect( action: 'importTemplate' );
     51                }
     52               
     53                def numTemplates = xml.@count;
     54
     55                if( !xml.template ) {
     56                        flash.message = "No templates could be found in the imported file. Please specify an XML template file.";
     57                        redirect( action: 'importTemplate' );
     58                }
     59
     60                // Check whether the templates already exist
     61                def templates = []
     62                def id = 0;
     63                xml.template.each { template ->
     64                        try {
     65                                def t = Template.parse( template, authenticationService.getLoggedInUser() );
     66
     67                                def templateData = [:]
     68                                templateData.key = id++;
     69                                templateData.template = t
     70                                templateData.alternatives = []
     71
     72                                // If a template exists that equals this xml template , return it.
     73                                for( def otherTemplate in Template.findAllByEntity( t.entity ) ) {
     74                                        if( t.contentEquals( otherTemplate ) ) {
     75                                                templateData.alternatives << otherTemplate;
     76                                        }
     77                                }
     78
     79                                templates << templateData
     80                        } catch (Exception e) {
     81                                templates << [ template: null, error: "Template " + ( template.name ?: " without name" ) + " could not be parsed"];
     82                        }
     83                }
     84
     85                // Save templates in session in order to have data available in the next (import) step
     86                session.templates = templates
     87               
     88                [templates: templates]
     89        }
     90
     91        /**
     92         * Saves the imported templates that the user has chosen
     93         */
     94        def saveImportedTemplates = {
     95                def ids = params.selectedTemplate
     96                def templates = session.templates
     97                def messages = []
     98               
     99                // Save all selected templates
     100                ids.each { id ->
     101                        def templateData = templates.find { template -> template.key == id.toLong() }
     102                        def importTemplate = templateData?.template
     103                        println id + ": " + templateData
     104
     105                        if( !importTemplate ) {
     106                                messages << "Template with id " + id + " could not be found."
     107                        } else {
     108                                def originalName = importTemplate.name
     109                                def newName = null
     110
     111                                // Check whether a new name has been given
     112                                if( params[ 'templateNames_' + id ] && params[ 'templateNames_' + id ] != importTemplate.name ) {
     113                                        importTemplate.name = params[ 'templateNames_' + id ]
     114                                        newName = params[ 'templateNames_' + id ]
     115                                }
     116
     117                                if( importTemplate.save() ) {
     118                                        messages << "Template " + originalName + " saved" + ( newName ? " as " + newName : "" )
     119                                } else {
     120                                        messages << "Template " + originalName + " could not be saved"
     121                                }
     122                        }
     123                }
     124
     125                // Remove templates from the session
     126                session.templates = null
     127
     128                [messages: messages]
     129        }
     130
     131        /**
     132         * Shows a form to select templates for export
     133         */
     134        def export = {
     135                // If the templates are already selected, export them
     136                if( params.templates ) {
     137                        if( !( params.templates instanceof String ) ) {
     138                                params.templates = params.templates.join(",")
     139                        }
     140
     141                        switch( params.type ) {
     142                                case "xml":
     143                                default:
     144                                        xml();
     145                                        return
     146                        }
     147                        return;
     148                } else {
     149                        [templates: Template.findAll()]
     150                }
     151        }
     152
     153        /**
     154         * XML Export of given templates, or all templates if no templates are given
     155         */
     156        def xml = {
     157                def templates
     158                if( params.templates ) {
     159                        def ids = [];
     160                        params.templates.split(",").each { ids << it.toLong() }
     161
     162                        def c = Template.createCriteria()
     163                        templates = c {
     164                                'in'("id", ids)
     165                        }
     166                } else {
     167                        templates = Template.findAll()
     168                }
     169               
     170                response.setHeader "Content-disposition", "attachment; filename=templates.xml"
     171
     172                render(view: 'xml', contentType:"text/xml", model: [templates: templates])
     173        }
     174
    19175}
  • trunk/grails-app/controllers/dbnp/studycapturing/TemplateEditorController.groovy

    r1175 r1257  
    2828    def entityName;
    2929    def entity;
    30         def AuthenticationService
     30        def authenticationService
    3131
    3232        /**
  • trunk/grails-app/domain/dbnp/studycapturing/Template.groovy

    r1027 r1257  
    123123
    124124        /**
     125         * Check whether the contents of the other template and the current template are equal.
     126         * For this check the name, description and owner don't matter. Also, the order of
     127         * template fields doesn't matter
     128         *
     129         * @return      true iff this template and the other template are used for the same entity and
     130         *                      the template contain the same template fields
     131         */
     132        public boolean contentEquals( Template otherTemplate ) {
     133                if( otherTemplate == this )
     134                        return true
     135
     136                if( otherTemplate == null )
     137                        return false
     138
     139                if( otherTemplate.entity != this.entity )
     140                        return false
     141
     142                // Check all template fields
     143                if( otherTemplate.fields?.size() != this.fields?.size() )
     144                        return false
     145
     146                if( otherTemplate.fields != null && this.fields != null ) {
     147                        for( def field in this.fields ) {
     148                                def fieldFound = false;
     149                                for( def otherField in otherTemplate.fields ) {
     150                                        if( otherField.contentEquals( field ) ) {
     151                                                fieldFound = true;
     152                                                break
     153                                        }
     154                                }
     155
     156                                if( !fieldFound ) {
     157                                        return false
     158                                }
     159                        }
     160                }
     161
     162                // If all tests pass, the objects are content-equal
     163                return true
     164        }
     165
     166        /**
    125167         * Look up the type of a certain template subject field
    126168         * @param String fieldName The name of the template field
     
    210252                return results
    211253        }
     254
     255        /**
     256         * Create a new template based on the parsed XML object.
     257         *
     258         * @see grails.converters.XML#parse(java.lang.String)
     259         * @throws IllegalArgumentException
     260         * @throws ClassNotFoundException
     261         *
     262         */
     263        public static parse(Object xmlObject, SecUser loggedInUser) {
     264                def t = new Template();
     265                t.name = xmlObject?.name?.text()
     266                t.description = xmlObject?.description?.text()
     267
     268                // Check whether a correct entity is given. The parseEntity method might
     269                // throw a ClassNotFoundException, but that is OK, it should be thrown if the class
     270                // doesn't exist.
     271                def entity = TemplateEntity.parseEntity( xmlObject.entity?.text() );
     272                if( !entity ) {
     273                        throw new Exception( "Incorrect entity given" );
     274                }
     275
     276                t.entity = entity
     277
     278                // Set the owner to the currently logged in user
     279                t.owner = loggedInUser
     280
     281                // Set all template fields
     282                xmlObject.templateFields.templateField.each {
     283                        def field = TemplateField.parse( it, entity );
     284
     285                        // Check whether a similar field already exists. For that, we search in all
     286                        // template fields with the same name, in order to have as little comparisons
     287                        // as possible
     288                        for( def otherField in TemplateField.findAllByName( field.name ) ) {
     289                                if( field.contentEquals( otherField ) ) {
     290                                        field = otherField;
     291                                        break;
     292                                }
     293                        }
     294                       
     295                        t.addToFields( field );
     296                }
     297
     298                return t
     299        }
    212300}
  • trunk/grails-app/domain/dbnp/studycapturing/TemplateEntity.groovy

    r1245 r1257  
    781781                }
    782782        }
     783
     784    /**
     785     * Returns a Class object given by the entityname, but only if it is a subclass of TemplateEntity
     786         *
     787         * @return A class object of the given entity, null if the entity is not a subclass of TemplateEntity
     788         * @throws ClassNotFoundException
     789     */
     790    static Class parseEntity( String entityName ) {
     791                if( entityName == null )
     792                        return null
     793
     794        // Find the templates
     795        def entity = Class.forName(entityName, true, Thread.currentThread().getContextClassLoader())
     796
     797        // succes, is entity an instance of TemplateEntity?
     798        if (entity?.superclass =~ /TemplateEntity$/ || entity?.superclass?.superclass =~ /TemplateEntity$/) {
     799            return entity;
     800        } else {
     801            return null;
     802        }
     803
     804    }
    783805}
  • trunk/grails-app/domain/dbnp/studycapturing/TemplateField.groovy

    r1213 r1257  
    366366        }
    367367
     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                        t.addToListEntries( new TemplateFieldListItem( name: it.name?.text() ) );
     477                }
     478                return t;
     479        }
     480
     481
    368482}
  • trunk/grails-app/views/common/_topnav.gsp

    r1150 r1257  
    2828                        <li><g:link controller="templateEditor" action="sample" params="${[standalone: true]}">Sample templates</g:link></li>
    2929                        <li><g:link controller="templateEditor" action="assay" params="${[standalone: true]}">Assay templates</g:link></li>
     30                        <li><g:link controller="template" action="export">Export</g:link></li>
     31                        <li><g:link controller="template" action="importTemplate">Import</g:link></li>
    3032                </ul>
    3133        </li>
  • trunk/grails-app/views/templateEditor/elements/_disabledFieldForm.gsp

    r1175 r1257  
    1818
    1919          <label for="type">Extra ontologies:<br /><br /><a href="#" style="text-decoration: underline;" onClick="openOntologyDialog();">Add new</a></label>
    20                 <g:select multiple="yes" size="5" from="${ontologies - templateField?.getUsedOntologies()}" class="ontologySelect" optionValue="name" optionKey="id" name="ontologies" id="ontologies_${templateField?.id}" value="${templateField?.getNonUsedOntologies()}" /><br />
     20                <g:select multiple="yes" size="5" from="${ (ontologies ?: [])- templateField?.getUsedOntologies()}" class="ontologySelect" optionValue="name" optionKey="id" name="ontologies" id="ontologies_${templateField?.id}" value="${templateField?.getNonUsedOntologies()}" /><br />
    2121
    2222        </div>
  • trunk/test/unit/dbnp/studycapturing/TemplateFieldTests.groovy

    r959 r1257  
    22
    33import grails.test.*
     4import dbnp.data.Ontology
    45
    56class TemplateFieldTests extends GrailsUnitTestCase {
     
    185186            fail();
    186187        }
     188    }
     189
     190    void testContentEquals() {
     191                // Check whether the fields matter
     192                TemplateField tf1 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Weight', type: TemplateFieldType.LONG, unit: 'kg', comments: 'Weight field' )
     193                TemplateField tf2 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Weight', type: TemplateFieldType.LONG,  unit: 'kg', comments: 'Weight field 2' )
     194                TemplateField tf3 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Length', type: TemplateFieldType.LONG,  unit: 'm', comments: 'Length field' )
     195                TemplateField tf4 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Length', type: TemplateFieldType.LONG,  unit: 'm', comments: 'Length field', required: true )
     196                TemplateField tf5 = new TemplateField( entity: dbnp.studycapturing.Study, name: 'Length', type: TemplateFieldType.LONG,  unit: 'm', comments: 'Length field', required: true )
     197
     198                TemplateField tf6 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Species', type: TemplateFieldType.ONTOLOGYTERM )
     199                TemplateField tf7 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Species', type: TemplateFieldType.ONTOLOGYTERM )
     200
     201                TemplateField tf8 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Species', type: TemplateFieldType.STRINGLIST )
     202                TemplateField tf9 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Species', type: TemplateFieldType.STRINGLIST )
     203
     204                mockDomain( TemplateField, [tf1, tf2, tf3, tf4, tf5, tf6, tf7, tf8, tf9] );
     205
     206                assert( tf1.contentEquals( tf1 ) );
     207                assert( tf1.contentEquals( tf2 ) );
     208                assert( tf2.contentEquals( tf1 ) );
     209                assert( !tf1.contentEquals( tf3 ) );
     210                assert( !tf3.contentEquals( tf4 ) );
     211                assert( !tf5.contentEquals( tf4 ) );
     212
     213                // Test ontology fields
     214                Ontology o1 = new Ontology( ncboId: 1000, ncboVersionedId: 14192, name: "Ontology 1" )
     215                Ontology o2 = new Ontology( ncboId: 1000, ncboVersionedId: 14192, name: "Ontology 2" )
     216                Ontology o3 = new Ontology( ncboId: 1000, ncboVersionedId: 5123, name: "Ontology 3" )
     217                Ontology o4 = new Ontology( ncboId: 4123, ncboVersionedId: 14192, name: "Ontology 4" )
     218
     219                tf6.addToOntologies( o1 )
     220
     221                // Different number of ontologies
     222                assert( !tf6.contentEquals( tf7 ) );
     223
     224                tf7.addToOntologies( o1 );
     225
     226                // Same ontologies
     227                assert( tf6.contentEquals( tf7 ) );
     228
     229                tf7.ontologies.clear()
     230                tf7.addToOntologies( o2 );
     231
     232                // Ontologies with the same ncboId
     233                assert( !tf6.contentEquals( tf7 ) );
     234
     235                tf6.ontologies.clear(); tf7.ontologies.clear()
     236                tf6.addToOntologies( o1 )
     237                tf6.addToOntologies( o4 )
     238                tf7.addToOntologies( o4 );
     239                tf7.addToOntologies( o1 );
     240
     241                // Different order but same ontologies
     242                assert( tf6.contentEquals( tf7 ) );
     243
     244                // Test listentries
     245
     246                assert( tf8.contentEquals( tf9 ) );
     247
     248                TemplateFieldListItem l1 = new TemplateFieldListItem( name: 'string1' );
     249                TemplateFieldListItem l2 = new TemplateFieldListItem( name: 'string1' );
     250                TemplateFieldListItem l3 = new TemplateFieldListItem( name: 'string2' );
     251                TemplateFieldListItem l4 = new TemplateFieldListItem( name: 'string3' );
     252
     253                tf8.addToListEntries( l1 );
     254
     255                // Different number of list entries
     256                assert( !tf8.contentEquals( tf9 ) );
     257
     258                tf9.addToListEntries( l1 );
     259
     260                // Same list entries
     261                assert( tf8.contentEquals( tf9 ) );
     262
     263                tf9.listEntries.clear();
     264                tf9.addToListEntries( l2 );
     265
     266                // Different list entries with the same name
     267                assert( tf8.contentEquals( tf9 ) );
     268
     269                tf9.listEntries.clear();
     270                tf9.addToListEntries( l3 );
     271
     272                // Different list entries
     273                assert( !tf8.contentEquals( tf9 ) );
     274
     275                // Same entries but different order
     276                tf8.listEntries.clear();
     277                tf9.listEntries.clear();
     278                tf8.addToListEntries( l2 );
     279                tf8.addToListEntries( l3 );
     280                tf9.addToListEntries( l3 );
     281                tf9.addToListEntries( l2 );
     282
     283                // Different order but same list entries
     284                assert( tf8.contentEquals( tf9 ) );
    187285
    188286    }
  • trunk/test/unit/dbnp/studycapturing/TemplateTests.groovy

    r959 r1257  
    22
    33import grails.test.*
     4import dbnp.authentication.*
    45
    56class TemplateTests extends GrailsUnitTestCase {
     
    2728                assert t2.inUse();
    2829                assert !t3.inUse();
     30    }
     31
     32    void testContentEquals() {
     33                // Basic checks
     34                Template t1 = new Template( entity: dbnp.studycapturing.Subject );
     35                Template t2 = new Template( entity: dbnp.studycapturing.Subject );
     36                Template t3 = new Template( entity: dbnp.studycapturing.Study );
     37
     38                mockDomain( Template, [t1, t2, t3] );
     39
     40                assert t1.contentEquals( t1 );
     41                assert t1.contentEquals( t2 );
     42                assert t2.contentEquals( t1 );
     43                assert !t3.contentEquals( t1 );
     44                assert !t2.contentEquals( t3 );
     45
     46                // Check whether other fields matter
     47                t1.name = "Test 1";
     48                t1.description = "Long description"
     49                t1.owner = new SecUser( username: "TestUser" )
     50                t2.name = "Test 2";
     51                t2.description = "Short description"
     52
     53                assert( t1.contentEquals( t2 ) );
     54
     55                // Check whether the fields matter
     56                TemplateField tf1 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Weight', type: TemplateFieldType.LONG,  unit: 'kg', comment: 'Weight field' )
     57                TemplateField tf2 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Weight', type: TemplateFieldType.LONG,  unit: 'kg', comment: 'Weight field 2' )
     58                TemplateField tf3 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'Length', type: TemplateFieldType.LONG,  unit: 'm', comment: 'Length field' )
     59                TemplateField tf4 = new TemplateField( entity: dbnp.studycapturing.Subject, name: 'BMI', type: TemplateFieldType.LONG,  unit: 'kg/m2', comment: 'BMI field', required: true )
     60
     61                mockDomain( TemplateField, [tf1, tf2, tf3, tf4] );
     62
     63                t1.addToFields( tf1 );
     64                t2.addToFields( tf1 );
     65
     66                // Same fields
     67                assert( t1.contentEquals( t2 ) );
     68
     69                t1.fields.clear();
     70                t1.addToFields( tf2 );
     71
     72                // Fields with equal properties
     73                assert( t1.contentEquals( t2 ) );
     74
     75                t1.fields.clear();
     76                t1.addToFields( tf3 );
     77
     78                // Fields with inequal properties
     79                assert( !t1.contentEquals( t2 ) );
     80
     81                t1.addToFields( tf1 );
     82                t2.addToFields( tf3 );
     83
     84                // Same fields in different order
     85                assert( t1.contentEquals( t2 ) );
     86
     87                t1.addToFields( tf4 )
     88
     89                // Different number of fields
     90                assert( !t1.contentEquals( t2 ) );
    2991
    3092    }
     93
    3194}
  • trunk/web-app/css/templateEditor.css

    r1136 r1257  
    129129.longTitle { display: none; }
    130130
    131 
    132 
    133131/* Add Ontology dialog */
    134132#addOntology { list-style-type: none; padding-left: 0; }
     
    137135#addOntology li .check { float: left; margin-left: -15px; margin-top: 2px; }
    138136
     137/* Importer */
     138#importTemplates h3 { margin-top: 0; }
     139#importTemplates ol.fields { margin-top: 0; }
     140#importTemplates .otherTemplateName { display: none; }
     141#importTemplates .error { color: #AA0000; }
Note: See TracChangeset for help on using the changeset viewer.