source: trunk/grails-app/services/dbnp/studyexport/ExportService.groovy @ 1568

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

Added support for matching tempaltes that are present in system.
In this way, template related information can in principle be imported, if the template is known in the current application.

File size: 9.8 KB
Line 
1/**
2 *  ExporterService
3 * 
4 *  @author Jahn
5 * 
6 *  This service for exporting a Study and all domain objects depending on it to XML.
7 */ 
8
9
10
11package dbnp.studyexport
12
13import dbnp.authentication.*
14import dbnp.studycapturing.*
15import grails.converters.XML
16import groovy.util.slurpersupport.*
17import org.dbnp.gdt.*
18
19class ExportService 
20{
21
22    static transactional = true
23    static scope = "session"
24
25
26    def grailsApplication
27    /**
28     *  List of classes that recursion does not go further into when building an XML 
29     *  document.
30         * 
31         *  @see #getRelatedObjects().
32     */ 
33        def static IgnoredClasses = [ String, long, Date, Boolean, SecUser, Publication, SecUser ]
34
35
36    /**
37     *  List of classes that recursion does not go further into when building an XML 
38     *  document; the elements are still included.
39         * 
40         *  (For importing Study objects)
41         * 
42         *  @see #getRelatedObjects().
43     */ 
44        def static TerminalClasses = [ AssayModule, Identity, Ontology, PersonAffiliation, 
45                        PersonRole, Template, TemplateField, 
46                        TemplateFieldListItem, TemplateFieldType, Term ] 
47
48
49    /**
50     *  List of domain classes related to Study.
51     */ 
52        def static DomainClasses = [ 'RegistrationCode':dbnp.authentication.RegistrationCode,
53                        'SecRole':dbnp.authentication.SecRole, 'SecUser':dbnp.authentication.SecUser,
54                        'SecUserSecRole':dbnp.authentication.SecUserSecRole,
55                        'SessionAuthenticatedUser':dbnp.authentication.SessionAuthenticatedUser,
56                        'Assay':dbnp.studycapturing.Assay,
57                        'Event':dbnp.studycapturing.Event, 'EventGroup':dbnp.studycapturing.EventGroup,
58                        'PersonAffiliation':dbnp.studycapturing.PersonAffiliation,
59                        'Person':dbnp.studycapturing.Person,
60                        'PersonRole':dbnp.studycapturing.PersonRole,
61                        'Publication':dbnp.studycapturing.Publication,
62                        'Sample':dbnp.studycapturing.Sample,
63                        'SamplingEvent':dbnp.studycapturing.SamplingEvent,
64                        'Study':dbnp.studycapturing.Study,
65                        'StudyPerson':dbnp.studycapturing.StudyPerson,
66                        'Subject':dbnp.studycapturing.Subject,
67                        'AssayModule':org.dbnp.gdt.AssayModule,
68                        'Identity':org.dbnp.gdt.Identity,
69                        'RelTime':org.dbnp.gdt.RelTime,
70                        'TemplateEntity':org.dbnp.gdt.TemplateEntity,
71                        'TemplateField':org.dbnp.gdt.TemplateField,
72                        'TemplateFieldListItem':org.dbnp.gdt.TemplateFieldListItem,
73                        'TemplateFieldType':org.dbnp.gdt.TemplateFieldType,
74                        'Template':org.dbnp.gdt.Template ]
75
76
77        /**
78         *  Returns a list of all Grails domain objects relevant for creating a full
79         *  XML representation of a Study.
80         * 
81         *  The actual XML is then created by the controller using Grails' XML converter.
82         * 
83         *  @param Study
84         *
85         *  @return List of all Grails domain objects
86         */ 
87
88        def getDependentObjects( Study study ) {
89                return getRelatedObjects( study )
90        }
91
92
93        /**
94         *  Returns a list of Grails domain objects. 
95         * 
96         *  Helper method for getDependentObjects().
97         * 
98         *  This method produces a list of all objects that need to be
99         *  written out in order to get an XML representation of a Study object.
100         * 
101         *  This is achieved by recursion. The recursion stops at objects whose
102         *  class is member of IgnoredClasses or TerminalClasses.
103         * 
104         *      Example call:
105         * 
106         *              def objects = getDependentObjects( Study.get(1) )
107         *              (objects*.class).unique().sort().each { println it }
108         * 
109         *  @param domainObject  A grails domain object.
110         * 
111         *  @return List of all Grails domain objects
112         */ 
113
114        def     getRelatedObjects( domainObject ) {
115
116                if(domainObject==null) {
117                        return []
118                }
119
120                def domainClass = domainObject.class
121                def objects = []
122
123
124                if( IgnoredClasses.contains(domainClass) )   {
125                        return objects
126                }
127
128                if( domainClass.toString()==~/class dbnp.authentication.SecUser.+/ || 
129                    domainClass.toString()==~/class dbnp.studycapturing.Publication.+/ ) {
130                        return objects
131                }
132
133
134                if(domainObject instanceof TemplateEntity ) {
135                        objects.push(domainObject.template)
136                        domainObject.template.fields.each { objects.push(it) }
137                }
138
139
140                if( TerminalClasses.contains(domainClass) )  {
141                        objects.push(domainObject)
142                        return objects
143                }
144
145                objects.push(domainObject)
146
147                                                                                                // enter recursion with regular domain fields
148                domainObject.properties.domainFields.each { field ->
149                        objects.addAll( getRelatedObjects(field) )
150                }
151
152                                                                                                // enter recursion with hasMany fields
153                domainObject.getProperties().hasMany.each { property, theClass ->
154
155                        boolean isTemplateField = ( domainObject instanceof TemplateEntity  && 
156                                property==~/template(.+)Fields/ ) 
157                        if( !isTemplateField ) {
158                                domainObject."$property".each { 
159                                                objects.addAll( getRelatedObjects(it) )
160                                }
161                        }
162
163                }
164                return objects.unique()
165        }
166
167
168
169
170        /**
171         *  Parse XML object to List of objects to be translated into
172         *  a Study by getStudy().
173         * 
174         *  @param Study
175         *
176         *  @return List of all Grails domain objects
177         *
178         *  @see getStudy()
179         */ 
180
181        def     parseXMLStudy() {
182                XML.parse(new FileInputStream('/tmp/test.xml'), "UTF-8")
183        }
184
185
186
187        def     parseXMLStudyList( GPathResult result ) {
188                def parseObjects = 
189                        result.childNodes().collect { 
190                                new ParseObject(it) 
191                        }               
192                def study = createStudy( parseObjects ) 
193                study.save()   
194        }
195
196
197
198
199        /**
200         *  Create Study from list of objects repesenting a Study.
201         * 
202         *  (For importing Study objects)
203         * 
204         *  @param  List of objects representing a Study.
205         *
206         *  @return void
207         */ 
208        def createStudy( List parseObjects ) {
209                parseObjects.each{ 
210                        populateOneToManies( it.domainObject, it.node, parseObjects )
211                }
212                parseObjects.each{ 
213                        if( it.domainObject instanceof TemplateEntity ) {
214                                addTemplateRelations( it.domainObject, it.node, parseObjects )
215                                addTemplateFields( it.domainObject, it.node, parseObjects )
216                        }
217                }
218        }
219
220
221        /** Set a TemplateEntity's template field. Find the right Template in the list 
222          *     of ParseObjects based on parsed id. If the TemplateEntity instance does
223          * not have an matching template in the list of ParseObjects, it remains empty.
224          *
225          * @param domainObject Some Template Entity
226          *
227          * @param node Node with parse information
228          *
229          * @param parseObjects List of ParseObjects
230          */
231        def addTemplateRelations( TemplateEntity domainObject, Node node, List parseObjects ) {
232                def id = node.children().find{it.name=='template'}?.attributes()?.id
233                if(id) {
234                        def template = parseObjects.find{ it.theClass==Template && it.id==id }?.domainObject
235                        if(template) {
236                                domainObject.template = template
237                        }
238                }
239        }
240
241
242
243        /** Set a TemplateEntity's template fields with values from a Node.
244          * The template fields are fields such as TemplateStringField or TemplateFieldType.
245          *
246          * @param domainObject Some Template Entity
247          *
248          * @param node Node with parse information
249          *
250          */
251        def addTemplateFields( TemplateEntity domainObject, Node node ) {
252                domainObject.metaClass.getProperties().each{ property ->
253                        def name = property.name      // name of templateFields, e.g., templateStringFields
254                        if( name ==~/template(.+)Fields/ ) {
255                                node.children().find{it.name==name}?.children()?.each{ fieldNode ->     
256                                        def key = fieldNode.attributes()?.key
257                                        def value = fieldNode.text()
258                                        //domainObject.setFieldValue(key,value)  -> needs to be fixed based on class/type
259                                }
260                        }
261                }
262        }
263
264
265
266        /**
267         *  Populate one-to-many maps of a new domainObject 
268         *  from list of ParseObjects.
269         * 
270         *  (For importing Study objects)
271         * 
272         *  @param domainObject   domainObject to be fielled 
273         * 
274         *  @param  List of parseObjects representing a Study.
275         *
276         *  @return the new domainObject
277         */ 
278        def populateOneToManies( domainObject, node, parseObjects ) {
279                if( !domainObject.class.metaClass.getProperties().find{ it.name=='hasMany' } ) {
280                        return
281                }
282
283                domainObject.class.hasMany.each{ name, theClass ->
284                        node.children().each { child -> 
285                                if(child.name==name) {
286                                        child.children().each { grandChild ->
287                                                def id = grandChild.attributes.id
288                                                if(id) {
289                                                        def ref = parseObjects.find{ it.theClass==theClass && it.id==id }
290                                                        if(ref) {
291                                                                def addTo = "addTo" + name.replaceFirst(name[0],name[0].toUpperCase()) 
292                                                                domainObject.invokeMethod(addTo,(Object) ref.domainObject )
293                                                        }
294                                                }
295                                        }
296                                }
297                        }
298                }
299        }
300
301
302
303        /**
304         *  Populate one-to-many maps of a new domainObject  from list of ParseObjects.
305         *
306         *  (For importing Study objects)
307         *
308         *  @param domainObject   domainObject to be fielled 
309         * 
310         *  @param  List of parseObjects representing a Study.
311         *
312         *  @return the new domainObject
313         */ 
314
315        private class ParseObject { 
316                String tag
317                String id
318                Class theClass
319                Object domainObject
320                Node node
321
322
323                public ParseObject( node ){
324                        tag = node.name()
325                        theClass = getClassForTag( tag ) 
326                        domainObject = theClass.newInstance() 
327                        id = null
328                        if(node.attributes && node.attributes.id) {
329                                id = node.attributes.id
330                        }
331                        this.node=node
332
333                        if(theClass==Template) {
334                                                                // Templates are suppsed to have been imported before
335                                                                // importing a study. Study.template is matched to a
336                                                                // Template by the template's name.
337                                def child = node.children.find{ "name"==it.name }
338                                domainObject = Template.findByName( child.text() )
339                        }
340                        else { 
341                                setSimpleFields()
342                        }
343                }
344
345
346
347                /**
348                 *  Populate this.domainObject's String and Date fields of
349                 *  a domainObject from parsed XML node.
350                 */ 
351                private void setSimpleFields() {
352
353                        def fields = 
354                                domainObject.getProperties().domainFields.collect { it.toString() }
355
356                        def map = [:]
357                        domainObject.metaClass.getProperties().each { property ->
358                                def name = property.name
359                                def field = fields.find{ it == name }
360
361                                if(field) { 
362                                        def type = property.type
363                                        def value = node.children().find{ it.name == field }.text()
364
365                                        switch(type) {
366                                                case String: map[field]=value; break
367                                                case Date: map[field] = Date.parse( 'yyyy-MM-dd', value ); break
368                                                //case Boolean: ???; break
369                                        }
370                                }
371                        }
372
373                        def newDomainObject = domainObject.class.newInstance(map)
374                        newDomainObject.id = 1  // neccessary?
375                        domainObject = newDomainObject
376                }
377
378        }
379
380
381}
Note: See TracBrowser for help on using the repository browser.