source: trunk/grails-app/controllers/StudyXMLController.groovy @ 1698

Last change on this file since 1698 was 1698, checked in by j.saito@…, 9 years ago

StudyXMLService and corresponding controller ready for hand over to Siemen.
The code is still preliminary. Much of the controller source should move into the service.

File size: 12.7 KB
Line 
1import dbnp.authentication.*
2import dbnp.studycapturing.*
3import dbnp.studycapturing.*
4import dbnp.studycapturing.Study
5import grails.converters.XML
6import grails.util.GrailsUtil
7import groovy.util.* 
8import groovy.util.slurpersupport.*
9import groovy.util.slurpersupport.Node as Node
10import java.util.ArrayList 
11import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationHolder;
12import org.dbnp.bgdt.*
13import org.dbnp.gdt.*
14
15
16/**  This class is for testing and developing the StudyXML service.
17 *   It should be merged into the service as soon as the service is operating.
18 *   
19 *   @author Jahn
20 */
21class StudyXMLController {
22
23    def studyXMLService
24
25        def DomainClasses = [ 
26                'Assay':dbnp.studycapturing.Assay,
27                'AssayModule':org.dbnp.gdt.AssayModule,
28                'Event':dbnp.studycapturing.Event,
29                'EventGroup':dbnp.studycapturing.EventGroup,
30                'Identity':org.dbnp.gdt.Identity,
31                'PersonAffiliation':dbnp.studycapturing.PersonAffiliation,
32                'Person':dbnp.studycapturing.Person,
33                'PersonRole':dbnp.studycapturing.PersonRole,
34                'Publication':dbnp.studycapturing.Publication,
35                'RegistrationCode':dbnp.authentication.RegistrationCode,
36                'RelTime':org.dbnp.gdt.RelTime,
37                'Sample':dbnp.studycapturing.Sample,
38                'SamplingEvent':dbnp.studycapturing.SamplingEvent,
39                'SecRole':dbnp.authentication.SecRole, 
40                'SecUser':dbnp.authentication.SecUser,
41                'SecUserSecRole':dbnp.authentication.SecUserSecRole,
42                'SessionAuthenticatedUser':dbnp.authentication.SessionAuthenticatedUser,
43                'Study':dbnp.studycapturing.Study,
44                'StudyPerson':dbnp.studycapturing.StudyPerson,
45                'Subject':dbnp.studycapturing.Subject,
46                'TemplateEntity':org.dbnp.gdt.TemplateEntity,
47                'TemplateFieldListItem':org.dbnp.gdt.TemplateFieldListItem,
48                'TemplateField':org.dbnp.gdt.TemplateField,
49                'TemplateFieldType':org.dbnp.gdt.TemplateFieldType,
50                'Template':org.dbnp.gdt.Template
51        ]
52
53
54        def index = {
55
56                if( GrailsUtil.environment!="development" ) {
57                        render "XML export/import is only available in the development environment."
58                        return
59                }
60
61                testExportImport()
62    }
63
64
65
66
67        /* This function gives an example of how to export and import with XML
68         * using the method defined in this controller and the StudyXML service
69     * This is only for testing. All functionality should move into the StudyXML
70         * service. */ 
71        def testExportImport() {
72
73                def list = studyXMLService.getDependentObjects( Study.get(1) ).unique() 
74
75                def xml = (list as XML)
76
77                def outf = new OutputStreamWriter(new FileOutputStream('/tmp/test.xml'))
78                outf<<xml
79                outf.close()
80
81                def inf = new File('/tmp/test.xml')
82                def lines = inf.readLines()
83                def buf = new StringBuffer()
84                lines.each{ buf.append(it) }
85
86                def stuff = new XmlSlurper().parseText(buf.toString()) 
87                def domainObjects = parseXMLStudyList(stuff)
88                def study = createStudy(domainObjects)
89                def owner = SecUser.findAll()?.get(1)
90                def person = StudyPerson.findAll()?.get(0)
91                setStudyParameters(study,"Free style",owner,person)
92
93
94                if( !study.validate() ) {
95                        study.errors.each { render it.toString() + '<br>' }
96                }
97
98
99                try{
100                        study.save()
101                } catch (Exception e) {
102                        println e
103                }
104
105                render "added study with code ${study.code}<br>"
106        }
107
108
109       
110        /** Get a class name for a given tag of the XML dump of a study.
111          * This just makes the first letter of the tag upper case and
112          * adds the package name.
113          *
114          * @param Tag
115          *
116          * @return Class corresponding to tag
117          */
118        def getClassForTag( String tag ) {
119                        def shortName = tag.replaceFirst( tag[0], tag[0].toUpperCase() )
120                        return DomainClasses[ shortName ] 
121        }
122
123
124
125        /** Create a study object from a list of domain objects that are
126          * parsed from XML. The study is not saved when returned and
127          * still needs to be validated. Also, it can still be updated.
128          *
129          * @param parseObjects List of domain objects parsed from XML.
130          *
131          * @return Study object, still unvalidated and transient
132          */
133        def     parseXMLStudyList( GPathResult result ) {
134                def parseObjects = 
135                        result.childNodes().collect { 
136                                new ParseObject(it) 
137                        }               
138                return parseObjects
139        }
140
141
142        /** Create a study object from a list of domain objects that are
143          * parsed from XML. The study is not saved when returned and
144          * still needs to be validated. Also, it can still be updated.
145          *
146          * @param parseObjects List of domain objects parsed from XML.
147          *
148          * @return Study object, still unvalidated and transient
149          */
150        def createStudy( List parseObjects ) {
151
152                treatConstraints(parseObjects) 
153
154                parseObjects.each{ 
155                        if(!it.isFromDatabase)
156                                linkDomainFields( it.domainObject, it.node, parseObjects )
157                }
158                parseObjects.each{ 
159                        if(!it.isFromDatabase)
160                                populateOneToManies( it.domainObject, it.node, parseObjects )
161                }
162                parseObjects.each{ 
163                        if(!it.isFromDatabase)
164                                if( it.domainObject instanceof TemplateEntity ) {
165                                        addTemplateRelations( it.domainObject, it.node, parseObjects )
166                                        addTemplateFields( it.domainObject, it.node )
167                                }
168                }
169
170                def study = parseObjects*.domainObject.find { it instanceof Study }
171
172                return study
173        }
174
175
176
177        /** Given a study object that has just been assabled from parsed xml,
178          * some fields have to be set manually. This is done here.
179          *
180          * @param study
181          *
182          * @param code - String value for study code. By default, the parsed string is used.
183          *
184          * @param owner - the owner of the study
185          *
186          * @param studyPerson - one StudyPerson has to be supplied since this informatin is
187          *                      not supposed to be retrieved from the xml.
188          */
189        def setStudyParameters(Study study, code=null, SecUser owner, studyPerson ) {
190
191                // set subject.species by hand
192                study.subjects.each { subject ->
193                        subject.species = Term.findAll()?.get(0)
194                }
195
196                // set samplingTemple for samplingEvents
197                study.samplingEvents.each { samplingEvent ->
198                        samplingEvent.sampleTemplate = Template.findAll()?.get(0)
199                }
200
201                // set study.studyPerson from database
202                study.persons.clone().each {
203                        study.removeFromPersons(it)
204                }
205                study.addToPersons( studyPerson )
206
207                study.owner = owner
208
209                if(code!=null) {
210                        study.code = code
211                }
212
213                return study
214        }
215
216
217
218
219        /** Some domain objects require special tweaking by hand before they
220          * can be imported from xml. This tuning is done here. For example,
221          * Assays are instantiated from the database if they exist and can be
222          * identified by its name.
223          *
224          * @param List of parse objects
225          **/
226        def treatConstraints( List parseObjects ) {
227
228                parseObjects.each { it ->
229                        def domainObject = it.domainObject
230                        switch( domainObject ) {
231                                case Assay: 
232                                                        // if assay with name name exists in database,
233                                                        // this object is replaced by the object in the database.
234                                        def existingAssay = Assay.findByName( domainObject.name )
235                                        if(existingAssay!=null) {       
236                                                it.domainObject = existingAssay
237                                                it.isFromDatabase = true
238                                        }
239                                        break;
240                        }
241                }
242        }       
243
244
245        /** Set a TemplateEntity's template field. Find the right Template in the list 
246          *     of ParseObjects based on parsed id. If the TemplateEntity instance does
247          * not have a matching template in the list of ParseObjects, it remains empty.
248          *
249          * @param domainObject Some Template Entity
250          *
251          * @param node Node with parse information
252          *
253          * @param parseObjects List of ParseObjects
254          **/
255        def addTemplateRelations( TemplateEntity domainObject, Node node, List parseObjects ) {
256                def id = node.children().find{it.name=='template'}?.attributes()?.id
257                if(id) {
258                        def template = parseObjects.find{ it.theClass==Template && it.id==id }?.domainObject
259                        if(template) {
260                                domainObject.template = template
261                        }
262                }
263        }
264
265
266
267        /** Set a TemplateEntity's template fields with values from a Node.
268          * The template fields are fields such as TemplateStringField or TemplateFieldType.
269          *
270          * @param domainObject Some Template Entity
271          *
272          * @param node Node with parse information
273          *
274          **/
275        def addTemplateFields( TemplateEntity domainObject, Node node ) {
276                domainObject.metaClass.getProperties().each{ property ->
277                        def name = property.name      // name of templateFields, e.g., templateStringFields
278                        if( name ==~/template(.+)Fields/ ) {
279                                node.children().find{it.name==name}?.children()?.each{ fieldNode ->     
280                                        def key = fieldNode.attributes()?.key
281                                        def value = fieldNode.text()
282                                        //domainObject.setFieldValue(key,value)  -> needs to be fixed based on class/type
283                                }
284                        }
285                }
286        }
287
288
289
290        // maybe not needed at all!!
291        // Link domain fields that are neither one-to-many nor many-to-one
292        def linkDomainFields( domainObject, node, List parseObjects ) {
293                                               
294                def fields = 
295                        domainObject.getProperties().domainFields.collect { it.toString() }
296
297                // deal with simple references to other domainObjects
298                // (no one-to-manys, no belongs-to, no has-many
299                // is this needed at all?
300                node.children.each { child ->
301                        def name = child.name 
302                        def id = child.attributes()?.id
303                        if(id) {
304                                        def list = parseObjects.findAll{ it.id == id }
305                                        list.each {
306                                                def property = it.theClass.metaClass.getProperties().find{ it.name == name }
307                                                if(property?.getType()==it.theClass) {
308                                                        domainObject."$name" = linkedDomainObject
309                                                }
310                                        }
311                        }
312                }
313        }
314
315       
316
317    /**
318      *
319      *  We are creating a new domain object called domainObject.
320          *  This method fills domainObject's to-many fields using the addTo() method.
321      *  The newly added objects are from a given list. They are identified
322          *  by their IDs stored while parsing the XML document.
323          * 
324      *  @param domainObject - a domain Object.
325      *  @param Node - the XML parse node from which the object is constructed.
326      *  @param List - a list of ParseObjects from which links may have to be made.
327          * 
328          *  Remark: this could become a member method of ParseObject.
329          */
330        def populateOneToManies( Object domainObject, Node node, List parseObjects ) {
331
332                if( domainObject.class==Template ) {
333                        return
334                }
335
336                if( !domainObject.class.metaClass.getProperties().find{ it.name=='hasMany' } ) {
337                        return
338                }
339
340                domainObject.class.hasMany.each{ name, theClass ->
341                        node.children().each { child -> 
342                                if(child.name==name) {
343                                        child.children().each { grandChild ->
344                                                def id = grandChild.attributes.id
345                                                if(id) {
346                                                        def ref = parseObjects.find{ it.theClass==theClass && it.id==id }
347                                                        if(ref) {
348                                                                def addTo = "addTo" + name.replaceFirst(name[0],name[0].toUpperCase()) 
349                                                                domainObject.invokeMethod(addTo,(Object) ref.domainObject )
350                                                        }
351                                                }
352                                        }
353                                }
354                        }
355                }
356        }
357
358
359        /** This private class contains all information corresponding to one domain objects
360     *  that is read from XML and then created to be linked into a new study object.
361     *
362     *  A parse object contains three importants pieces of information: (1) the xml node,
363         *  (2) the name of the tag that corresponds to a class name, and (3) the id that
364         *  represents the instance of the exporetd object. There are several more members
365         *  that are needed in order to reconstruct the study object.
366     */
367        private class ParseObject { 
368
369                String tag               // the tag used in the XML document for the object under construction.
370                String id                // id for this object found in the XML document.
371                Class theClass           // Class of this domain object.
372                Object domainObject      // the domain object under construction or retrieved from the db.
373                Node node                // an XML parse node.
374                boolean isFromDatabase   // true if domainObject is from db, false if it's from xml input.
375
376
377                /** The constructor gets an XML parse node and fills all other members from that.
378                  */
379                public ParseObject( Node node ){
380                        tag = node.name()
381                        theClass = getClassForTag( tag ) 
382                        domainObject = theClass.newInstance() 
383                        isFromDatabase = false
384                        id = null
385                        if(node.attributes && node.attributes.id) {
386                                id = node.attributes.id
387                        }
388                        this.node=node
389
390                        if(theClass==Template) {
391                                                                // Templates have been imported before
392                                                                // importing a study. Study.template is matched to a
393                                                                // Template by the template's name.
394                                def child = node.children.find{ "name"==it.name }
395                                domainObject = Template.findByName( child.text() )
396                        }
397                        else { 
398                                setSimpleFields()
399                        }
400
401                }
402
403
404                private void setSimpleFields() {
405
406                        def fields = [] 
407                        node.children().each{ 
408                                if(it.text()!="") {
409                                        fields.add(it.name)
410                                }
411                        }
412
413                        def map = [:]
414                        domainObject.metaClass.getProperties().each { property ->
415                                def name = property.name
416                                def field = fields.find{ it == name }
417
418                                if(field) { 
419                                        def type = property.type
420                                        def value = node.children().find{ it.name == field }.text()
421
422                                        switch(type) {
423                                                case String: map[field]=value; break
424                                                case Date: map[field] = Date.parse( 'yyyy-MM-dd', value ); break
425                                                //default: println "${theClass}: ${field} has type ${type}"
426                                                //case Boolean: ???; break
427                                        }
428                                }
429
430                        }
431
432                        def newDomainObject = domainObject.class.newInstance(map)
433                        newDomainObject.id = 1
434                        domainObject = newDomainObject
435                }
436
437        }
438
439
440}
Note: See TracBrowser for help on using the repository browser.