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

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

Minor changes in documentation.

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