Permalink
Browse files

custom domain class marshall configurations

  • Loading branch information...
2 parents 5da3b90 + 466d7b2 commit 41ef6919752a5d6d879f1a6451cbe75dd61bef63 Predrag Knezevic committed Apr 25, 2012
View
@@ -2,3 +2,6 @@
.project
.settings
target
+/.link_to_grails_plugins
+/.link_to_grails_plugins/*
+/target-eclipse
@@ -13,9 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
+import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU;
+import org.codehaus.groovy.grails.support.proxy.DefaultProxyHandler;
+import org.codehaus.groovy.grails.support.proxy.ProxyHandler;
import org.grails.plugins.marshallers.ExtendedConvertersConfigurationInitializer
+import org.grails.plugins.marshallers.GenericDomainClassJSONMarshaller;
+import org.grails.plugins.marshallers.GenericDomainClassXMLMarshaller;
import org.grails.plugins.marshallers.XmlMarshallerArtefactHandler
import org.grails.plugins.marshallers.JsonMarshallerArtefactHandler
+import org.grails.plugins.marshallers.config.MarshallingConfig;
+import org.grails.plugins.marshallers.config.MarshallingConfigBuilder;
+import grails.converters.XML;
+import grails.converters.JSON;
+
class MarshallersGrailsPlugin {
// the plugin version
@@ -36,7 +46,10 @@ class MarshallersGrailsPlugin {
def scm = [url: "http://github.com/pedjak/grails-marshallers"]
def licence = "APACHE"
- def artefacts = [ XmlMarshallerArtefactHandler, JsonMarshallerArtefactHandler ]
+ def artefacts = [
+ XmlMarshallerArtefactHandler,
+ JsonMarshallerArtefactHandler
+ ]
def author = "Predrag Knezevic"
def authorEmail = "pedjak@gmail.com"
@@ -62,4 +75,7 @@ Further documentation can be found <a href="http://github.com/pedjak/grails-mars
}
}
+
+
+
}
View
110 README.md
@@ -123,5 +123,113 @@ when XML element name should be custom, e.g.
does not follow marshaller artifact convention, e.g.:
register CustomAXMLSerializer
+
+Configuring Domain Class Marshalling
+------------------------------------
+
+Along to developing and registering a custom marshaller, the way how a
+domain class instance is marshalled can be specified within the domain
+class itself - specifying the marshalling configuration(s).
+
+Let's assume that you have the following domain classes which has to be serialized
+
+ class Author {
+ String name
+ Date dob
+ List books
+ }
+
+ class Book {
+ String isbn
+ String name
+ }
+
+and we have the following requirements
+
+* *dob* and *isbn* fields has to be serialized as attributes
+* *books* belonging to an *author* has to be serialized as children of *author* xml element
+* there is a static method of Author class which fetches popular books for an author. These popular books should also be serialized as children of *author* xml element
+
+Marshaling configuration for such a case is specified as a static closure of each class
+
+ class Author {
+ static marshalling={
+ xml {
+ export {
+ ignoreIdentifier true
+ attribute 'dob'
+ deep 'books'
+ virtual {
+ popularBooks {author,xml->
+ author.findPopularBooks().each{ popularBook->
+ xml.startNode(xml.getElementName(popularBook))
+ xml.lookupObjectMarshaller(popularBook).marshalObject(popularBook,xml)
+ xml.end()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ String name
+ Date dob
+ List books
+ }
+
+
+ class Book {
+ static marshalling={
+ xml {
+ export {
+ elementName 'my-book'
+ attribute 'isbn'
+ }
+ }
+ }
+
+ String isbn
+ String name
+ }
+
+
+In the above specified configuration:
+
+* *xml* is the serialization format. Currently, only *xml* is supported, but it is planned to support *json* format also
+* *export* is the identifier for named converter configuration. Could be any name or *default* which identifies default converter configuration.
+
+Within the named configuration closure there are several configuration options possible:
+
+* *ignoreIdentifier* when true will suppress serialization of domain object identifier
+* *identifier* is a comma separated list of fields which uniquely identifies a domain object in case database id is not sufficient.
+* *elementName* configures a custom domain object element name which should be used instead of default one
+* *attribute* is a comma separated list of field names which will be serialized as attributes of domain object element
+* *deep* is a comma separated list of field names. If a field representing one-to-many relation is marked as *deep*, all contained data of related objects will be serialized
+* *virtual* is a configuration option allows us to define closures with custom serialization behavior
+
+When configuration is defined as above the following snippet of code would perform actual serialization
+
+ Author author=Author.findByName('Jonathan Franzen')
+ XML.use('export')
+ XML converter=new XML(author)
+ String xml=converter.toString()
+
+Producing the following hypothetical XML output
-
+ <?xml version="1.0" encoding="UTF-8"?>
+ <author dob="Fri Aug 17 00:00:00 CET 1959">
+ <name>Jonathan Franzen</name>
+ <books>
+ <my-book isbn="1111">
+ <name>The Twenty-Seventh City</name>
+ <my-book>
+ <my-book isbn="2222">
+ <name>Freedom</name>
+ <my-book>
+ </books>
+ <popularBooks>
+ <my-book isbn="2222">
+ <name>Freedom</name>
+ <my-book>
+ </popularBooks>
+ </author>
@@ -24,6 +24,7 @@ grails.project.dependency.resolution = {
//mavenRepo "http://repository.jboss.com/maven2/"
}
dependencies {
+ build "org.apache.ivy:ivy:2.2.0"
// specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
// runtime 'mysql:mysql-connector-java:5.1.13'
@@ -0,0 +1,12 @@
+package org.grails.plugins.marshallers
+
+public class ActionConfigBuilder{
+ def config=[:]
+ def methodMissing(String name,args){
+ if(args.size()==1 && args[0] instanceof Closure){
+ config[name]=args[0]
+ }else{
+ throw new IllegalStateException('Illegal syntax encountered while building serializers config: '+ name)
+ }
+ }
+}
@@ -17,24 +17,58 @@ package org.grails.plugins.marshallers
import grails.converters.JSON;
import grails.converters.XML;
-
+import grails.util.GrailsConfig;
+import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU;
+import org.codehaus.groovy.grails.commons.GrailsApplication
+import org.codehaus.groovy.grails.support.proxy.ProxyHandler
import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationHolder;
import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationInitializer;
import org.codehaus.groovy.grails.web.converters.configuration.DefaultConverterConfiguration;
+import org.grails.plugins.marshallers.config.MarshallingConfig
+import org.grails.plugins.marshallers.config.MarshallingConfigBuilder
+import org.springframework.core.convert.ConversionService;
/**
* @author Predrag Knezevic
* @version $Date: $
*/
class ExtendedConvertersConfigurationInitializer extends ConvertersConfigurationInitializer {
+
+
@Override
public void initialize() {
super.initialize()
processGrailsConfigurations()
}
protected def processGrailsConfigurations() {
+ def application=applicationContext.grailsApplication
+ ProxyHandler proxyHandler = applicationContext.getBean(ProxyHandler.class);
+ MarshallingConfigBuilder delegate=new MarshallingConfigBuilder();
+ def namedConfigs=new HashSet<String>();
+ application.domainClasses.each{
+ def mc=GCU.getStaticPropertyValue(it.clazz,'marshalling');
+ if(mc){
+ mc.setDelegate(delegate)
+ mc.call()
+ MarshallingConfig c=new MarshallingConfig(config:delegate.config);
+ ['xml', 'json'].each {type->namedConfigs<< c.getConfigNamesForContentType(type)}
+ }
+ }
+ namedConfigs.flatten().each{name->
+ if(name=='default'){
+ XML.registerObjectMarshaller(new GenericDomainClassXMLMarshaller('default',proxyHandler));
+ JSON.registerObjectMarshaller(new GenericDomainClassJSONMarshaller('default',proxyHandler));
+ }else{
+ XML.createNamedConfig(name) {
+ it.registerObjectMarshaller(new GenericDomainClassXMLMarshaller(name,proxyHandler));
+ }
+ JSON.createNamedConfig(name) {
+ it.registerObjectMarshaller(new GenericDomainClassJSONMarshaller(name,proxyHandler));
+ }
+ }
+ }
[xml: XML, json: JSON].each { type, converterClass ->
def marshallerCfg = applicationContext.grailsApplication.config?.grails?.plugins?.marshallers?."${type}"
processConfig(marshallerCfg, converterClass, type)
@@ -0,0 +1,36 @@
+package org.grails.plugins.marshallers
+import grails.converters.JSON
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.codehaus.groovy.grails.support.proxy.ProxyHandler;
+import org.codehaus.groovy.grails.web.converters.ConverterUtil;
+import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationHolder;
+import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException;
+import org.codehaus.groovy.grails.web.converters.marshaller.ObjectMarshaller;
+import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU;
+import org.codehaus.groovy.grails.commons.GrailsDomainConfigurationUtil;
+
+import org.codehaus.groovy.grails.web.converters.marshaller.json.DomainClassMarshaller;
+
+class GenericDomainClassJSONMarshaller implements ObjectMarshaller<JSON> {
+ private static Log LOG = LogFactory.getLog(GenericDomainClassJSONMarshaller.class);
+ private String configName;
+ private ProxyHandler proxyHandler;
+
+ public GenericDomainClassJSONMarshaller(String configName,ProxyHandler proxyHandler){
+ LOG.debug("Registered json domain class marshaller for $configName");
+ this.configName=configName;
+ this.proxyHandler=proxyHandler;
+ }
+
+ @Override
+ public boolean supports(Object object) {
+ def clazz=object.getClass();
+ return ConverterUtil.isDomainClass(clazz) && GCU.getStaticPropertyValue(clazz,'marshalling');
+ }
+
+ @Override
+ public void marshalObject(Object object,JSON converter) throws ConverterException {
+ }
+}
Oops, something went wrong.

0 comments on commit 41ef691

Please sign in to comment.