/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1147 $ by $Author: glycoslave $ on $Date:: 2009-06-04 #$
*/
package org.eurocarbdb.util;
// stdlib imports
import java.util.Map;
import java.util.HashMap;
import java.io.Writer;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.FileNotFoundException;
// 3rd party imports
import org.apache.log4j.Logger;
import freemarker.template.Template;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
// eurocarb imports
import org.eurocarbdb.dataaccess.EntityManager;
import org.eurocarbdb.dataaccess.EurocarbObject;
import org.eurocarbdb.dataaccess.core.GlycanSequence;
// static imports
import static java.util.Collections.synchronizedMap;
import static org.eurocarbdb.util.JavaUtils.checkNotNull;
import static org.eurocarbdb.dataaccess.Eurocarb.getEntityManager;
/**
* Serialises Eurocarb objects to and from XML.
*
* This implementation uses Freemarker templates as the primary
* driver of XML structure and content. Properties and associations
* of serialised objects are processed recursively in depth-first
* order.
*
*<h2>Typical usage</h2>
*<ol>
*<li>Serialise to an output {@link Writer} stream:
*<pre>
* GlycanSequence gs = ...;
* XmlSerialiser xmlio = new XmlSerialiser();
*
* // note: defaults to System.out unless specified anyway...
* xmlio.setWriter( System.out );
*
* xmlio.serialise( gs );
*</pre>
*</li>
*<li>Serialise to a {@link String}, using a {@link StringWriter}:
*<pre>
* GlycanSequence gs = ...;
* XmlSerialiser xmlio = new XmlSerialiser();
*
* // note: be aware that the xml produced may be *large*
* xmlio.setWriter( new StringWriter() );
*
* xmlio.serialise( gs );
*
* String xml = xmlio.getWriter().toString();
*</pre>
*</li>
*</ol>
*
*<h2>Freemarker templates</h2>
*<p>
* The templates for processed objects are expected to be named
* after the class of the object being serialised, plus ".xml.ftl".
* For example, serialising a {@link org.eurocarbdb.dataaccess.core.GlycanSequence}
* object will look for a template "org/eurocarbdb/dataaccess/core/GlycanSequence.xml.ftl"
* in the classpath. If this template isn't found, templates corresponding to
* each of the superclasses of the serialised object will be looked for.
*</p>
*<p>
* Templates are provided with the following Freemarker variables:
* <ul>
* <li>x - reference to the object currently being serialised</li>
* <li>xmlio - reference to the XmlSerialiser object itself</li>
* <li>show_detail - boolean indicating whether to produce brief or detailed XML for
* the current object</li>
* </ul>
*</p>
*<p>
* Regarding the <tt>show_detail</tt>flag: "brief" XML is intended to provide
* a minimal XML representation of an object, enough to establish a link to
* that object, but little actual object data. It must at least contain the
* object's main id as an attribute, eg:
* <pre>
* <glycan id="1234" />
* <reference id="1234" type="journal" ref-name="pubmed" ref-id="9999" />
* </pre>
* "detailed" XML is intended to contain all the information shown in the brief
* representation, plus all/most object data.
*</p>
*
* @author mjh
*/
public class XmlSerialiser
{
/** logging handle */
static Logger log = Logger.getLogger( XmlSerialiser.class );
/** Freemarker {@link Template} factory. */
static Configuration freemarkerConfig = null;
/** Hash of Class to template name for Class. */
private static Map<Class,Template> cache
= synchronizedMap( new HashMap<Class,Template>() );
/** Output Writer for XML. */
private Writer out;
/** Returns the Writer for output. Defaults to {@link System#out}. */
public Writer getWriter()
{
if ( out == null )
out = new OutputStreamWriter( System.out );
return out;
}
/** Sets the Writer for output. */
public void setWriter( Writer out )
{
this.out = out;
}
/**
* Outputs detailed XML for the given object to the {@link Writer}
* given by {@link #getWriter()}.
*/
public void serialise( EurocarbObject x ) throws Exception
{
serialise( x, true );
}
/**
* Outputs brief or detailed XML for the given object to the {@link Writer}
* given by {@link #getWriter()}.
*/
public void serialise( EurocarbObject x, boolean showDetail ) throws Exception
{
Template template = getTemplate( x.getClass() );
if ( template == null )
{
log.warn("No XML template for object of class " + x.getClass() );
out.write("<!-- no xml written to handle objects of "
+ x.getClass()
+ " -->");
return;
}
optimise( x );
Map<String,Object> data = new HashMap<String,Object>();
data.put("x", x );
data.put("xml", this );
data.put("show_detail", showDetail );
template.process( data, this.getWriter() );
this.getWriter().flush();
}
/**
* Returns the Freemarker {@link Template} for the given {@link Class}.
*/
protected Template getTemplate( Class<?> c )
throws IOException
{
if ( cache.containsKey( c ) )
return cache.get( c );
Template t = findTemplate( c );
// even if it's null, we cache it to save looking it up next time.
cache.put( c, t );
return t;
}
private Template findTemplate( Class<?> c )
throws IOException
{
checkNotNull( c );
if ( freemarkerConfig == null )
{
freemarkerConfig = new Configuration();
freemarkerConfig.setObjectWrapper( new DefaultObjectWrapper() );
}
String template_name = getTemplateName( c );
log.debug("trying to load template: " + template_name );
Template t = null;
try
{
t = freemarkerConfig.getTemplate( template_name );
if ( t != null )
{
return t;
}
}
catch ( FileNotFoundException ex )
{
log.debug("couldn't find a template for " + template_name );
c = c.getSuperclass();
return ( c != null )
? getTemplate( c )
: null;
}
return t;
}
/**
* Returns the pathname of the Freemarker template file for the
* given {@link Class}. Current implementation uses the full name
* of the passed class plus ".xml.ftl".
*/
protected String getTemplateName( Class<?> c )
{
freemarkerConfig.setClassForTemplateLoading( c, "" );
return c.getSimpleName() + ".xml.ftl";
}
protected void optimise( Object x )
{
getEntityManager().populate( x );
}
} // end class XmlSerialiser