commons-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From b p <trashspam2...@yahoo.com>
Subject Re: specifying .betwixt file to use
Date Mon, 23 Aug 2004 16:29:56 GMT
I had a similar requirement and started down the path of creating an introspector that had
a single xml file that held all the mappings and could be passed in at runtime. As requirements
changed on me, I didn't end up needing to really use it, but I had the basic idea implemented.
  You are welcome to use the code below as is or take some ideas from it (it could probably
still use a little cleaning up in the caching of mappings and registering of beans).   There
is a test case attached as well that should show you how to use it.
 
Robert, as always you are welcome to commit all or any pieces of the code that you think would
be useful for betwixt.
 
-Brian
 
 
______________________________________________
XMLSingleMappingFileBeanInfoDigester.java

package org.apache.commons.betwixt.digester;
import org.apache.commons.betwixt.digester.AddDefaultsRule;
import org.apache.commons.betwixt.digester.AttributeRule;
import org.apache.commons.betwixt.digester.ElementRule;
import org.apache.commons.betwixt.digester.HideRule;
import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
import java.util.HashMap;
/**
 * <code>XMLSingleMappingFileBeanInfoDigester</code> is a digester of XML files
 * containing XMLBeanInfo definitions for a JavaBeans.
 *
 * @author Brian Pugh
 */
public class XMLSingleMappingFileBeanInfoDigester extends XMLBeanInfoDigester {
  private HashMap beanInfoMap = new HashMap();
  // Implementation methods
  //-------------------------------------------------------------------------
  /**
   * Reset configure for new digestion.
   */
  protected void configure() {
    if (!configured) {
      configured = true;
      // add the various rules
      addRule("betwixt-config", new ConfigRule());
      addRule("betwixt-config/class", new ClassRule());
      addRule("*/element", new ElementRule());
      addRule("*/attribute", new AttributeRule());
      addRule("*/hide", new HideRule());
      addRule("*/addDefaults", new AddDefaultsRule());
    }
    // now initialize
    //setAttributesForPrimitives(true);
    getProcessedPropertyNameSet().clear();
    getXMLIntrospector().getRegistry().flush();
  }
  /**
   * Map containing <code>XMLBeanInfo</code> classes.
   * Keys are the <code>Class</code> and values are the <code>XMLBeanInfo</code>
objects.
   *
   * @return map of XMLBeanInfos
   */
  public HashMap getBeanInfoMap() {
    return beanInfoMap;
  }
  /**
   * Set the Map containing <code>XMLBeanInfo</code> classes.
   * @param beanInfoMap   map to set.
   */
  public void setBeanInfoMap(HashMap beanInfoMap) {
    this.beanInfoMap = beanInfoMap;
  }
}

-------------------------------------------------------
ClassRule.java

package org.apache.commons.betwixt.digester;
import org.apache.commons.betwixt.XMLBeanInfo;
import org.apache.commons.betwixt.digester.RuleSupport;
import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.util.HashMap;
/**
 * Digester Rule to process class elements.
 * @author Brian Pugh
 */
public class ClassRule extends RuleSupport {
  /** Logger. */
  private static final Log log = LogFactory.getLog(ClassRule.class);

  /** Base constructor. */
  public ClassRule() {
  }
  // Rule interface
  //-------------------------------------------------------------------------
  /**
   * Process the beginning of this element.
   *
   * @param attributes The attribute list of this element
   * @throws org.xml.sax.SAXException if the primitiveTypes attribute contains an invalid
value
   */
  public void begin(Attributes attributes) throws SAXException {
    String className = attributes.getValue("name");
    if (className == null || "".equals(className)) {
      throw new SAXException("Invalid 'class' element.  "
                             + "Attribute 'name' is required but was not found but was not
found.");
    }
    try {
      Class beanClass = Class.forName(className);
      XMLBeanInfo xmlBeanInfo = new XMLBeanInfo(beanClass);
      XMLBeanInfoDigester xmlBeanInfoDigester =(XMLBeanInfoDigester) getDigester();
      xmlBeanInfoDigester.setBeanClass(beanClass);
      xmlBeanInfoDigester.push(xmlBeanInfo);
    }
    catch (ClassNotFoundException e) {
      throw new SAXException("Invalid 'class' element.  Unable to find class: " + className,
e);
    }
  }
  /**
   * Process the end of this element.
   */
  public void end() {
    XMLBeanInfo xmlBeanInfo = (XMLBeanInfo) getDigester().pop();
    XMLSingleMappingFileBeanInfoDigester digester = (XMLSingleMappingFileBeanInfoDigester)
getDigester();
    HashMap xmlBeanInfoMapping = digester.getBeanInfoMap();
    xmlBeanInfoMapping.put(xmlBeanInfo.getBeanClass(), xmlBeanInfo);
    digester.setBeanClass(null);
  }
}

-------------------------------------------
ConfigRule.java

package org.apache.commons.betwixt.digester;
import org.apache.commons.betwixt.digester.RuleSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.util.HashMap;
/**
 * Digester Rule to process config elements.
 * @author Brian Pugh
 */
public class ConfigRule extends RuleSupport {
  /** Logger. */
  private static final Log log = LogFactory.getLog(ConfigRule.class);

  /** Base constructor. */
  public ConfigRule() {
  }
  // Rule interface
  //-------------------------------------------------------------------------
  /**
   * Process the beginning of this element.
   *
   * @param attributes The attribute list of this element
   * @throws org.xml.sax.SAXException if the primitiveTypes attribute contains an invalid
value
   */
  public void begin(Attributes attributes) throws SAXException {
    String value = attributes.getValue("primitiveTypes");
    if (value != null) {
      if (value.equalsIgnoreCase("element")) {
        getXMLInfoDigester().setAttributesForPrimitives(false);
      }
      else if (value.equalsIgnoreCase("attribute")) {
        getXMLInfoDigester().setAttributesForPrimitives(true);
      }
      else {
        throw new SAXException(
          "Invalid value inside element <betwixt-config> for attribute 'primitiveTypes'."
          + " Value should be 'element' or 'attribute'");
      }
    }
    XMLSingleMappingFileBeanInfoDigester digester = (XMLSingleMappingFileBeanInfoDigester)
getDigester();
    getDigester().push(digester.getBeanInfoMap());
  }
  /**
   * Process the end of this element.
   */
  public void end() {
    Object top = getDigester().pop();
  }

}
---------------------------------------------------
XMLSingleMappingFileIntrospector.java

package org.apache.commons.betwixt;
import org.apache.commons.betwixt.XMLBeanInfo;
import org.apache.commons.betwixt.XMLIntrospector;
import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
import org.apache.commons.betwixt.digester.XMLSingleMappingFileBeanInfoDigester;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.net.URL;
import java.util.HashMap;
/**
 * Introspector that uses a single mapping file to define all mappings (rather than seperate
.betwixt files for each class).
 * Mapping file format is very almost the same as standard .betwixt mapping file with the
addition that multiple classes
 * can be mapped in one file.
 * <p/>
 * Sample mapping:
 * <PRE>
 * <code>
 * &lt;?xml version="1.0"?&gt;
 * &lt;betwixt-config&gt;
 * &lt;!--name of the class to map --&gt;
 *  &lt;class name="org.some.package.MyClass"&gt;
 *  &lt;!-- standard definations (same as in standard .betwixt file)    --&gt;
 *    &lt;element name="repository-registration-result"&gt;
 *      &lt;element name="repository-id" property="repositoryId"/&gt;
 *      &lt;element name="id-mapping" property="idMapping" class="org.some.package.SomeOtherClass"/&gt;
 *      &lt;element name="status" property="status"/&gt;
 *      &lt;element name="exception" property="exception"/&gt;
 *      &lt;element name="primary-luid" property="primaryLuid"/&gt;
 *      &lt;addDefaults add-properties='false'/&gt;
 *    &lt;/element&gt;
 *  &lt;/class&gt;
 * ...
 *  &lt;!--additional class mappings --&gt;
 *  &lt;class&gt;
 * ...
 *  &lt;/class&gt;
 * ...
 * &lt;/betwixt-config&gt;
 * </code>
 * </PRE>
 *
 * @author Brian Pugh
 */
public class XMLSingleMappingFileIntrospector extends XMLIntrospector {
  /**
   * Log used for logging.
   */
  protected Log log = LogFactory.getLog(XMLSingleMappingFileIntrospector.class);
  /**
   * Digester used to parse the XML descriptor files.
   */
  private XMLBeanInfoDigester digester;
  private boolean mapLoaded = false;
  private String mappingFile;
  private HashMap beanInfoMap;

  /**
   * construct a XMLSingleMappingFileIntrospector.
   */
  public XMLSingleMappingFileIntrospector() {
  }
  /**
   * get the digester that will be used to interpret the mapping file.
   *
   * @return digester.
   */
  public XMLBeanInfoDigester getDigester() {
    return digester;
  }
  /**
   * Set the digester that will be used to interpret the mapping file.
   *
   * @param digester digester to use with this Introspector.
   */
  public void setDigester(XMLBeanInfoDigester digester) {
    this.digester = digester;
  }
  /**
   * Get the mapping file.
   *
   * @return mapping file name.
   */
  public String getMappingFile() {
    return mappingFile;
  }
  /**
   * Set the mapping file.  The name should follow the rules for a resource as defined in
<code>ClassLoader.getResource()</code>.
   *
   * @param mappingFile the name of the mapping file.
   * @see java.lang.ClassLoader
   */
  public void setMappingFile(String mappingFile) {
    this.mappingFile = mappingFile;
  }

  /**
   * Attempt to lookup the XML descriptor for the given class using the
   * mappingfile or return null if it could not be loaded.
   *
   * @param aClass class to create XMLBeanInfo for.
   * @return XMLBeanInfo digested from the mappingfile if one can be found.
   *         Otherwise null.
   */
  protected synchronized XMLBeanInfo findByXMLDescriptor(Class aClass) {
    if (!isMapLoaded()) {
      loadXmlBeanInfoMapping();
      if (beanInfoMap == null && log.isWarnEnabled()) {
        log.warn("Unable to load XMLBeanInfo classes");
      }
    }
    XMLBeanInfo xmlBeanInfo = null;
    if (beanInfoMap != null) {
      xmlBeanInfo = (XMLBeanInfo)beanInfoMap.get(aClass);
    }
    return xmlBeanInfo;
  }

  /**
   * Use <code>mappingFile</code> to create and load known <code>XMLBeanInfo</code>
classes.
   */
  public void loadXmlBeanInfoMapping() {
    ClassLoader loader = getClass().getClassLoader();
    URL url = null;
    if (loader != null && mappingFile != null) {
      url = loader.getResource(mappingFile);
      if (url == null) {
        url = loader.getResource("/" + mappingFile);
      }
    }
    if (url != null) {
      String urlText = url.toString();
      try {
        if (log.isDebugEnabled()) {
          log.debug("Parsing XML descriptor: " + urlText);
        }
        // synchronized method so this digester is only used by
        // one thread at once
        if (digester == null) {
          digester = new XMLSingleMappingFileBeanInfoDigester();
          digester.setXMLIntrospector(this);
        }
        beanInfoMap = (HashMap)digester.parse(urlText);
        mapLoaded = true;
      }
      catch (Exception e) {
        log.warn("Caught exception trying to parse: " + urlText, e);
        throw new RuntimeException("Unable to parse mapping file [" + mappingFile + "]", e);
      }
    }
    else {
      if (log.isWarnEnabled()) {
        log.warn("Could find XML descriptor file ["  + mappingFile + "].  No mapping file
will be used. ");
      }
    }
  }
  /**
   * Map containing loaded <code>XMLBeanInfo</code> classes.
   * Keys are the <code>Class</code> and values are the <code>XMLBeanInfo</code>
objects.
   *
   * @return map of XMLBeanInfos
   */
  public HashMap getBeanInfoMap() {
    return beanInfoMap;
  }
  /**
   * indicates whether the mappingFile has been read to load the <code>XMLBeanInfo</code>s.
   *
   * @return true if mapping file has been loaded.
   */
  public boolean isMapLoaded() {
    return mapLoaded;
  }

}
 
 
---------------------------
TestXMLSIngleMappingFileInstrospector.java

package org.apache.commons.betwixt;

import junit.framework.Test;
import junit.framework.TestSuite;
import junit.framework.TestCase;
import junit.textui.TestRunner;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.io.StringWriter;
import java.io.IOException;
import java.io.StringReader;
import java.beans.IntrospectionException;
import org.apache.commons.betwixt.io.BeanWriter;
import org.apache.commons.betwixt.io.BeanReader;
import org.xml.sax.SAXException;
/**
 * @author Brian Pugh
 */
public class TestXMLSIngleMappingFileInstrospector extends TestCase {
  public void testRoundTripWithSingleMappingFile() throws IOException, SAXException, IntrospectionException
{
    AddressBean addressBean = new AddressBean();
    addressBean.setCity("New York");
    addressBean.setCode("92342");
    addressBean.setCountry("USA");
    addressBean.setStreet("12312 Here");
    PartyBean partyBean = new PartyBean();
    partyBean.setDateOfParty(new Date());
    partyBean.setExcuse("too late");
    partyBean.setFromHour(22);
    partyBean.setVenue(addressBean);

    StringWriter outputWriter = new StringWriter();
    outputWriter.write("<?xml version='1.0' ?>\n");
    BeanWriter beanWriter = new BeanWriter(outputWriter);
    beanWriter.enablePrettyPrint();
    beanWriter.setWriteEmptyElements(true);
    XMLSingleMappingFileIntrospector xmlIntrospector = new XMLSingleMappingFileIntrospector();
    xmlIntrospector.setMappingFile("org/apache/commons/betwixt/mapping.xml");
    beanWriter.setXMLIntrospector(xmlIntrospector);
    beanWriter.write(partyBean);
    String expectedOut = "<?xml version='1.0' ?>\n" +
                     "  <party id=\"1\">\n" +
                     "    <the-excuse>too late</the-excuse>\n" +
                     "    <location id=\"2\">\n" +
                     "      <street>12312 Here</street>\n" +
                     "      <city>New York</city>\n" +
                     "      <code>92342</code>\n" +
                     "      <country>USA</country>\n" +
                     "    </location>\n" +
                     "    <time>22</time>\n" +
                     "  </party>\n";
    assertEquals(expectedOut, outputWriter.toString());
    BeanReader beanReader = new BeanReader();
    beanReader.setXMLIntrospector(xmlIntrospector);
    StringReader xmlReader = new StringReader(outputWriter.toString());
    registerBeanClass(beanReader, PartyBean.class);
    //Parse the xml
    PartyBean result = (PartyBean)beanReader.parse(xmlReader);
    assertEquals(partyBean.getExcuse(), result.getExcuse());
    assertEquals(partyBean.getFromHour(), result.getFromHour());
    AddressBean addressResult = result.getVenue();
    assertEquals(addressBean.getCity(), addressResult.getCity());
    assertEquals(addressBean.getCode(), addressResult.getCode());
    assertEquals(addressBean.getCountry(), addressResult.getCountry());
    assertEquals(addressBean.getStreet(), addressResult.getStreet());

  }
  /**
   * Register a bean class with the XML element as defined in the mapping file.  If no occurance
of the class
   * is found in the mapping file, use default registration.
   *
   * @param clazz class to register.
   */
  private void registerBeanClass(BeanReader beanReader, Class clazz) throws IntrospectionException
{
    boolean classMapped = false;
    XMLIntrospector introspector = beanReader.getXMLIntrospector();
    if (introspector != null) {
      if (introspector instanceof XMLSingleMappingFileIntrospector) {
        XMLSingleMappingFileIntrospector unifiedXMLIntrospector = (XMLSingleMappingFileIntrospector)introspector;
        HashMap beanInfoMap = unifiedXMLIntrospector.getBeanInfoMap();
        if (beanInfoMap != null) {
          for (Iterator i = beanInfoMap.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry)i.next();
            XMLBeanInfo xmlBeanInfo = (XMLBeanInfo)e.getValue();
            if (xmlBeanInfo.getBeanClass() == clazz) {
              beanReader.registerBeanClass(xmlBeanInfo.getElementDescriptor().getQualifiedName(),
xmlBeanInfo.getBeanClass());
              classMapped = true;
              break;
            }
          }
        }
      }
    }
    if (!classMapped) {
      throw new RuntimeException("Could not map the class '\" + e.getKey() + \" to a XMLBeanInfo.");
    }
  }

  public static Test suite() {
    return new TestSuite(TestXMLSIngleMappingFileInstrospector.class);
  }
  public static void main(String[] args) {
    TestRunner.run(suite());
  }
  

}

-----------------------------
mapping.xml (put in org.apache.commons.betwixt)
<?xml version="1.0"?>
<betwixt-config>
  <class name="org.apache.commons.betwixt.PartyBean">
    <element name="party">
      <element name="the-excuse" property="excuse"/>
      <element name="location" property="venue"/>      
      <element name="time" property="fromHour"/>
    </element>
  </class>
  <class name="org.apache.commons.betwixt.AddressBean">
    <element name="address">
      <element name="street" property="street"/>
      <element name="city" property="city"/>
      <element name="code" property="code"/>
      <element name="country" property="country"/>
    </element>
  </class>
</betwixt-config>


robert burrell donkin <robertburrelldonkin@blueyonder.co.uk> wrote:
On 20 Aug 2004, at 04:16, Evgueni_Iakhontov@national.com.au wrote:

> Hi All,
> I've been looking at mapping our java objects to xml using .betwixt 
> files.
> We have a number of xml formats that we want to use and we are reusing 
> same
> objects for each xml message. The question is - can I specify which
> .betwixt file I want to use when converting my java object to xml?

not at the moment but this is a feature that's on the todo radar. i 
might take a look at adding this over the next day or two..

there are some performance wrinkles that might need a little thought. 
loading, parsing then processing the betwixt file is slow. (which is 
why the registry exists.) however, it's probably better to add the 
feature now and worry later.

- robert


---------------------------------------------------------------------
To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-user-help@jakarta.apache.org


		
---------------------------------
Do you Yahoo!?
New and Improved Yahoo! Mail - 100MB free storage!
Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message