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

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

This is the new structure for importing / exporting studies from / to XML.
Currently, much functionality is in the Controller. This should all move back to the Service (studyXMLService).
However, as long as the code is in the controller, interactive debugging is much faster as modifying services often seems to require re-running the whole app.

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