Return-Path: Delivered-To: apmail-jakarta-commons-user-archive@www.apache.org Received: (qmail 46839 invoked from network); 23 Aug 2004 16:30:59 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur-2.apache.org with SMTP; 23 Aug 2004 16:30:59 -0000 Received: (qmail 19264 invoked by uid 500); 23 Aug 2004 16:30:05 -0000 Delivered-To: apmail-jakarta-commons-user-archive@jakarta.apache.org Received: (qmail 19120 invoked by uid 500); 23 Aug 2004 16:30:04 -0000 Mailing-List: contact commons-user-help@jakarta.apache.org; run by ezmlm Precedence: bulk List-Unsubscribe: List-Subscribe: List-Help: List-Post: List-Id: "Jakarta Commons Users List" Reply-To: "Jakarta Commons Users List" Delivered-To: mailing list commons-user@jakarta.apache.org Received: (qmail 19076 invoked by uid 99); 23 Aug 2004 16:30:03 -0000 X-ASF-Spam-Status: No, hits=0.9 required=10.0 tests=DNS_FROM_RFC_ABUSE,FROM_ENDS_IN_NUMS,HTML_40_50,HTML_MESSAGE X-Spam-Check-By: apache.org Received: from [216.136.172.71] (HELO web11705.mail.yahoo.com) (216.136.172.71) by apache.org (qpsmtpd/0.27.1) with SMTP; Mon, 23 Aug 2004 09:29:57 -0700 Message-ID: <20040823162956.80223.qmail@web11705.mail.yahoo.com> Received: from [12.110.19.201] by web11705.mail.yahoo.com via HTTP; Mon, 23 Aug 2004 09:29:56 PDT Date: Mon, 23 Aug 2004 09:29:56 -0700 (PDT) From: b p Subject: Re: specifying .betwixt file to use To: Jakarta Commons Users List In-Reply-To: <36AAE0FA-F47E-11D8-B07E-003065DC754C@blueyonder.co.uk> MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="0-44506483-1093278596=:79289" X-Virus-Checked: Checked X-Spam-Rating: minotaur-2.apache.org 1.6.2 0/1000/N --0-44506483-1093278596=:79289 Content-Type: text/plain; charset=us-ascii 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; /** * XMLSingleMappingFileBeanInfoDigester 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 XMLBeanInfo classes. * Keys are the Class and values are the XMLBeanInfo objects. * * @return map of XMLBeanInfos */ public HashMap getBeanInfoMap() { return beanInfoMap; } /** * Set the Map containing XMLBeanInfo 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 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. *

* Sample mapping: *

 * 
 * <?xml version="1.0"?>
 * <betwixt-config>
 * <!--name of the class to map -->
 *  <class name="org.some.package.MyClass">
 *  <!-- standard definations (same as in standard .betwixt file)    -->
 *    <element name="repository-registration-result">
 *      <element name="repository-id" property="repositoryId"/>
 *      <element name="id-mapping" property="idMapping" class="org.some.package.SomeOtherClass"/>
 *      <element name="status" property="status"/>
 *      <element name="exception" property="exception"/>
 *      <element name="primary-luid" property="primaryLuid"/>
 *      <addDefaults add-properties='false'/>
 *    </element>
 *  </class>
 * ...
 *  <!--additional class mappings -->
 *  <class>
 * ...
 *  </class>
 * ...
 * </betwixt-config>
 * 
 * 
* * @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 ClassLoader.getResource(). * * @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 mappingFile to create and load known XMLBeanInfo 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 XMLBeanInfo classes. * Keys are the Class and values are the XMLBeanInfo objects. * * @return map of XMLBeanInfos */ public HashMap getBeanInfoMap() { return beanInfoMap; } /** * indicates whether the mappingFile has been read to load the XMLBeanInfos. * * @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("\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 = "\n" + " \n" + " too late\n" + " \n" + " 12312 Here\n" + " New York\n" + " 92342\n" + " USA\n" + " \n" + " \n" + " \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) robert burrell donkin 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! --0-44506483-1093278596=:79289--