Commits

(no author)  committed 8cdc757

This commit was manufactured by cvs2svn to create branch 'os'.

git-svn-id: http://svn.opensymphony.com/svn/propertyset/branches/os@61bf3cbcdd-1c1a-0410-9a68-d6f521e3fa7b

  • Participants
  • Parent commits d7d6363
  • Branches os

Comments (0)

Files changed (3)

File src/java/com/opensymphony/module/propertyset/ejb/EJBPropertySet.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.module.propertyset.ejb;
+
+
+/* ====================================================================
+ * The OpenSymphony Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2001 The OpenSymphony Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        OpenSymphony Group (http://www.opensymphony.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "OpenSymphony" and "The OpenSymphony Group"
+ *    must not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact license@opensymphony.com .
+ *
+ * 5. Products derived from this software may not be called "OpenSymphony"
+ *    or "OSCore", nor may "OpenSymphony" or "OSCore" appear in their
+ *    name, without prior written permission of the OpenSymphony Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+import com.opensymphony.module.propertyset.*;
+
+import com.opensymphony.util.DataUtil;
+import com.opensymphony.util.EJBUtils;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.Serializable;
+
+import java.rmi.RemoteException;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.ejb.CreateException;
+
+import javax.naming.NamingException;
+
+
+/**
+ * The EJBPropertySet is an implementation of
+ * {@link com.opensymphony.module.propertyset.PropertySet} that
+ * uses Enterprise JavaBeans to store and retrieve Properties.
+ *
+ * <p>This class is a proxy to the
+ * {@link com.opensymphony.module.propertyset.ejb.PropertyStore}
+ * Session Bean that handles the PropertySet and behind the scenes
+ * delegates to various Entity Beans to persist the data in an
+ * efficient way.</p>
+ *
+ * <p>Each method in the proxy will catch any thrown
+ * {@link java.rmi.RemoteException} and rethrow it wrapped in a
+ * {@link com.opensymphony.module.propertyset.PropertyImplementationException} .</p>
+ *
+ * <h3>Usage</h3>
+ *
+ * <p>In order to use an EJBPropertySet, a PropertyStore Session Bean
+ * must first be retrieved that represents the PropertySet data. This
+ * is typically either returned by another EJB, or looked up using
+ * an JNDI location for PropertyStoreHome and an int ID for the actual
+ * PropertySet used.</p>
+ *
+ * <b>Required Args</b>
+ * <ul>
+ *  <li><b>entityId</b> - Long that holds the ID of this entity</li>
+ *  <li><b>entityName</b> - String that holds the name of this entity type</li>
+ * </ul>
+ * <p>
+ *
+ * <b>Optional Configuration</b>
+ * <ul>
+ *  <li><b>storeLocation</b> - the JNDI name for the PropertyStore EJB lookup (defaults to os.PropertyStore)</li>
+ * </ul>
+ *
+ * @author <a href="mailto:joe@wirestation.co.uk">
+ * @author <a href="mailto:plightbo@hotmail.com">Pat Lightbody</a>
+ * @version $Revision$
+ *
+ * @see com.opensymphony.module.propertyset.PropertySet
+ * @see com.opensymphony.module.propertyset.ejb.PropertyStore
+ * @see com.opensymphony.module.propertyset.ejb.PropertyStoreHome
+ */
+public class EJBPropertySet extends AbstractPropertySet implements Serializable {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log logger = LogFactory.getLog(EJBPropertySet.class);
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private PropertyStore store;
+    private String entityName;
+    private long entityId;
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+    * Proxy to {@link com.opensymphony.module.propertyset.ejb.PropertyStore#getKeys(java.lang.String,long,java.lang.String,int)}
+    */
+    public Collection getKeys(String prefix, int type) throws PropertyException {
+        try {
+            return store.getKeys(entityName, entityId, prefix, type);
+        } catch (RemoteException re) {
+            throw new PropertyImplementationException(re);
+        }
+    }
+
+    /**
+    * Proxy to {@link com.opensymphony.module.propertyset.ejb.PropertyStore#getType(java.lang.String,long,java.lang.String)}
+    */
+    public int getType(String key) throws PropertyException {
+        try {
+            return store.getType(entityName, entityId, key);
+        } catch (RemoteException re) {
+            throw new PropertyImplementationException(re);
+        }
+    }
+
+    /**
+    * Proxy to {@link com.opensymphony.module.propertyset.ejb.PropertyStore#exists(java.lang.String,long,java.lang.String)}
+    */
+    public boolean exists(String key) throws PropertyException {
+        try {
+            return store.exists(entityName, entityId, key);
+        } catch (RemoteException re) {
+            throw new PropertyImplementationException(re);
+        }
+    }
+
+    public void init(Map config, Map args) {
+        entityId = DataUtil.getLong((Long) args.get("entityId"));
+        entityName = (String) args.get("entityName");
+
+        String storeLocation = (String) config.get("storeLocation");
+
+        if (storeLocation == null) {
+            storeLocation = "PropertyStore";
+        }
+
+        try {
+            PropertyStoreHome home = (PropertyStoreHome) EJBUtils.lookup(storeLocation, PropertyStoreHome.class);
+            store = home.create();
+        } catch (NamingException e) {
+            e.printStackTrace();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        } catch (CreateException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+    * Proxy to {@link com.opensymphony.module.propertyset.ejb.PropertyStore#remove(java.lang.String,long,java.lang.String)}
+    */
+    public void remove(String key) throws PropertyException {
+        try {
+            store.removeEntry(entityName, entityId, key);
+        } catch (RemoteException re) {
+            throw new PropertyImplementationException(re);
+        }
+    }
+
+    /**
+    * Proxy to {@link com.opensymphony.module.propertyset.ejb.PropertyStore#set(java.lang.String,long,int,java.lang.String,java.io.Serializable)}
+    */
+    protected void setImpl(int type, String key, Object value) throws PropertyException {
+        try {
+            store.set(entityName, entityId, type, key, (Serializable) value);
+        } catch (RemoteException re) {
+            logger.error("RemoteExecption while setting property", re);
+            throw new PropertyImplementationException(re);
+        }
+    }
+
+    /**
+    * Proxy to {@link com.opensymphony.module.propertyset.ejb.PropertyStore#get(java.lang.String,long,int,java.lang.String)}
+    */
+    protected Object get(int type, String key) throws PropertyException {
+        try {
+            return store.get(entityName, entityId, type, key);
+        } catch (RemoteException re) {
+            throw new PropertyImplementationException(re);
+        }
+    }
+}

File src/java/com/opensymphony/module/propertyset/ejb/PropertyStoreEJB.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.module.propertyset.ejb;
+
+
+/* ====================================================================
+ * The OpenSymphony Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2001 The OpenSymphony Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        OpenSymphony Group (http://www.opensymphony.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "OpenSymphony" and "The OpenSymphony Group"
+ *    must not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact license@opensymphony.com .
+ *
+ * 5. Products derived from this software may not be called "OpenSymphony"
+ *    or "OSCore", nor may "OpenSymphony" or "OSCore" appear in their
+ *    name, without prior written permission of the OpenSymphony Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+import com.opensymphony.module.propertyset.*;
+import com.opensymphony.module.propertyset.ejb.types.PropertyEntryHomeFactory;
+import com.opensymphony.module.propertyset.ejb.types.PropertyEntryLocal;
+import com.opensymphony.module.propertyset.ejb.types.PropertyEntryLocalHome;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.Serializable;
+
+import java.util.*;
+
+import javax.ejb.*;
+
+import javax.naming.NamingException;
+
+
+/**
+ * Session bean implementation of PropertyStore.
+ *
+ * <p>Makes use of ValueEntityDelegator to determine which entity beans to use for
+ * appropriate types.</p>
+ *
+ * @ejb.bean
+ *  type="Stateless"
+ *  name="PropertyStore"
+ *  view-type="remote"
+ *  transaction-type="Container"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="PropertyEntry"
+ *  view-type="local"
+ *
+ * @ejb.permission unchecked="true"
+ * @ejb.transaction type="Supports"
+ *
+ * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
+ * @author <a href="mailto:hani@formicary.net">Hani Suleiman</a>
+ * @version $Revision$
+ *
+ * @see com.opensymphony.module.propertyset.ejb.PropertyStore
+ * @see com.opensymphony.module.propertyset.ejb.PropertyStoreHome
+ */
+public class PropertyStoreEJB implements SessionBean {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log logger = LogFactory.getLog(PropertyStoreEJB.class);
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private PropertyEntryLocalHome entryHome;
+
+    /*public void ejbPostCreate() throws CreateException {}*/
+    private SessionContext context;
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * @ejb.interface-method
+     */
+    public Collection getKeys(String entityName, long entityId, String prefix, int type) {
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debug("getKeys(" + entityName + "," + entityId + ")");
+            }
+
+            List results = new ArrayList();
+            Iterator entries = entryHome.findByNameAndId(entityName, entityId).iterator();
+
+            while (entries.hasNext()) {
+                PropertyEntryLocal entry = (PropertyEntryLocal) entries.next();
+                String key = entry.getKey();
+
+                if (((prefix == null) || key.startsWith(prefix)) && ((type == 0) || (type == entry.getType()))) {
+                    results.add(key);
+                }
+            }
+
+            Collections.sort(results);
+
+            return results;
+        } catch (FinderException e) {
+            logger.error("Could not find keys.", e);
+            throw new PropertyImplementationException(e);
+        }
+    }
+
+    public void setSessionContext(SessionContext ctx) {
+        try {
+            entryHome = PropertyEntryHomeFactory.getLocalHome();
+        } catch (NamingException e) {
+            logger.fatal("Could not lookup PropertyEntryHome.", e);
+            throw new EJBException(e);
+        }
+
+        this.context = ctx;
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public int getType(String entityName, long entityId, String key) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("getType(" + entityName + "," + entityId + ",\"" + key + "\")");
+        }
+
+        try {
+            return entryHome.findByEntity(entityName, entityId, key).getType();
+        } catch (ObjectNotFoundException e) {
+            return 0;
+        } catch (FinderException e) {
+            logger.error("Could not find type.", e);
+            throw new PropertyImplementationException(e);
+        }
+    }
+
+    public void ejbActivate() {
+    }
+
+    /**
+     * @ejb.create-method
+     */
+    public void ejbCreate() throws CreateException {
+    }
+
+    public void ejbPassivate() {
+    }
+
+    public void ejbRemove() {
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public boolean exists(String entityName, long entityId, String key) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("exists(" + entityName + "," + entityId + ",\"" + key + "\")");
+        }
+
+        return getType(entityName, entityId, key) != 0;
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public Serializable get(String entityName, long entityId, int type, String key) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("get(" + entityName + "," + entityId + "," + type + ",\"" + key + "\")");
+        }
+
+        try {
+            PropertyEntryLocal entry = entryHome.findByEntity(entityName, entityId, key);
+
+            if (type != entry.getType()) {
+                // type does not match
+                if (logger.isDebugEnabled()) {
+                    logger.debug("wrong property type");
+                }
+
+                throw new InvalidPropertyTypeException();
+            }
+
+            return entry.getValue(); // found it
+        } catch (ObjectNotFoundException e) {
+            // Property does not exist.
+            if (logger.isDebugEnabled()) {
+                logger.debug("no property found");
+            }
+
+            return null;
+        } catch (PropertyException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.error("Could not retrieve value.", e);
+            throw new PropertyImplementationException(e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public void removeEntry(String entityName, long entityId, String key) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("remove(" + entityName + "," + entityId + ",\"" + key + "\")");
+        }
+
+        try {
+            entryHome.findByEntity(entityName, entityId, key).remove();
+        } catch (ObjectNotFoundException e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Value did not exist anyway.");
+            }
+        } catch (PropertyException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.error("Could not remove value.", e);
+            throw new PropertyImplementationException("Could not remove value.", e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public void set(String entityName, long entityId, int type, String key, Serializable value) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("set(" + entityName + "," + entityId + "," + type + ",\"" + key + "\", [" + value + "] )");
+        }
+
+        // If null, remove value.
+        if (value == null) {
+            removeEntry(entityName, entityId, key);
+
+            return;
+        }
+
+        PropertyEntryLocal entry;
+
+        try {
+            entry = entryHome.findByEntity(entityName, entityId, key);
+
+            // if we get here, then a property with that key already exists
+            if (entry.getType() != type) { // verify existing property is of same type
+
+                if (logger.isWarnEnabled()) {
+                    logger.warn("property is of different type");
+                }
+
+                throw new DuplicatePropertyKeyException();
+            }
+        } catch (ObjectNotFoundException e) {
+            // property with that key does not yet exist
+            try {
+                entry = entryHome.create(entityName, entityId, type, key);
+            } catch (CreateException ce) {
+                logger.error("Could not create new property.", ce);
+                throw new PropertyImplementationException("Could not create new property.", ce);
+            }
+        } catch (PropertyException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.error("Could not set property.", e);
+            throw new PropertyImplementationException("Could not set property.", e);
+        }
+
+        entry.setValue(value);
+    }
+}

File src/java/com/opensymphony/module/propertyset/xml/XMLPropertySet.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.module.propertyset.xml;
+
+import com.opensymphony.module.propertyset.PropertyImplementationException;
+import com.opensymphony.module.propertyset.PropertySet;
+
+/* ====================================================================
+ * The OpenSymphony Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2001 The OpenSymphony Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        OpenSymphony Group (http://www.opensymphony.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "OpenSymphony" and "The OpenSymphony Group"
+ *    must not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact license@opensymphony.com .
+ *
+ * 5. Products derived from this software may not be called "OpenSymphony"
+ *    or "OSCore", nor may "OpenSymphony" or "OSCore" appear in their
+ *    name, without prior written permission of the OpenSymphony Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+import com.opensymphony.module.propertyset.memory.SerializablePropertySet;
+
+import com.opensymphony.util.*;
+
+import org.w3c.dom.*;
+
+import org.xml.sax.SAXException;
+
+import java.io.*;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import java.util.*;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+
+/**
+ * The XMLPropertySet behaves as an in-memory typed PropertySet, with the ability to
+ * load and save all the properties to/from an XML document.
+ *
+ * <ul>
+ * <li>boolean, int, long, double and String properties are saved as simple Text nodes.</li>
+ * <li>text and XML properties are stored as CDATA blocks.</li>
+ * <li>java.util.Date properties are stored in <code>yyyy-mm-dd HH:mm:ss</code> format.</li>
+ * <li>java.util.Properties properties are stored in child elements.</li>
+ * <li>java.lang.Object and byte[] data properties are encoded using base64 into text and stored as CDATA blocks.</li>
+ * </ul>
+ *
+ * <h3>Example:</h3>
+ * <blockquote><code>
+ * XMLPropertySet p = new XMLPropertySet(); // create blank property-set<br>
+ * p.load( new FileReader("my-properties.xml") ); // load properties from XML.<br>
+ * System.out.println( p.getString("name") );<br>
+ * p.setString("name","blah blah");<br>
+ * p.save( new FileWriter("my-properties.xml") ); // save properties back to XML.<br>
+ * </code></blockquote>
+ *
+ * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
+ * @version $Revision$
+ *
+ * @see com.opensymphony.module.propertyset.PropertySet
+ * @see com.opensymphony.module.propertyset.memory.SerializablePropertySet
+ */
+public class XMLPropertySet extends SerializablePropertySet {
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+    * Load properties from XML input.
+    */
+    public void load(Reader in) throws ParserConfigurationException, IOException, SAXException {
+        loadFromDocument(XMLUtils.parse(in));
+    }
+
+    /**
+    * Load properties from XML input.
+    */
+    public void load(InputStream in) throws ParserConfigurationException, IOException, SAXException {
+        loadFromDocument(XMLUtils.parse(in));
+    }
+
+    /**
+    * Load properties from XML document.
+    */
+    public void loadFromDocument(Document doc) throws PropertyImplementationException {
+        try {
+            NodeList nodeList = XMLUtils.xpathList(doc, "/property-set/property");
+
+            for (int i = 0; i < nodeList.getLength(); i++) {
+                Element e = (Element) nodeList.item(i);
+                String key = e.getAttribute("key");
+                int type = type(e.getAttribute("type"));
+                Object value = loadValue(e, key, type);
+
+                if (value != null) {
+                    setImpl(type, key, value);
+                }
+            }
+        } catch (TransformerException e) {
+            throw new PropertyImplementationException(e);
+        }
+    }
+
+    /**
+    * Save properties to XML output.
+    */
+    public void save(Writer out) throws ParserConfigurationException, IOException {
+        XMLUtils.print(saveToDocument(), out);
+    }
+
+    /**
+    * Save properties to XML output.
+    */
+    public void save(OutputStream out) throws ParserConfigurationException, IOException {
+        XMLUtils.print(saveToDocument(), out);
+    }
+
+    /**
+    * Save properties to XML Document.
+    */
+    public Document saveToDocument() throws ParserConfigurationException {
+        Document doc = XMLUtils.newDocument("property-set");
+        Iterator keys = getKeys().iterator();
+
+        while (keys.hasNext()) {
+            String key = (String) keys.next();
+            int type = getType(key);
+            Object value = get(type, key);
+            saveValue(doc, key, type, value);
+        }
+
+        return doc;
+    }
+
+    /**
+    * Load value from <property>...</property> tag. Null returned if value cannot be determined.
+    */
+    private Object loadValue(Element e, String key, int type) {
+        String text = XMLUtils.getElementText(e);
+
+        switch (type) {
+        case PropertySet.BOOLEAN:
+            return new Boolean(TextUtils.parseBoolean(text));
+
+        case PropertySet.INT:
+            return new Integer(TextUtils.parseInt(text));
+
+        case PropertySet.LONG:
+            return new Long(TextUtils.parseLong(text));
+
+        case PropertySet.DOUBLE:
+            return new Double(TextUtils.parseDouble(text));
+
+        case PropertySet.STRING:
+        case PropertySet.TEXT:
+            return text;
+
+        case PropertySet.DATE:
+
+            try {
+                return dateFormat.parse(text);
+            } catch (ParseException pe) {
+                return null; // if the date cannot be parsed, ignore it.
+            }
+
+        case PropertySet.OBJECT:
+
+            try {
+                return TextUtils.decodeObject(text);
+            } catch (Exception ex) {
+                return null; // if Object cannot be decoded, ignore it.
+            }
+
+        case PropertySet.XML:
+
+            try {
+                return XMLUtils.parse(text);
+            } catch (Exception ex) {
+                return null; // if XML cannot be parsed, ignore it.
+            }
+
+        case PropertySet.DATA:
+
+            try {
+                return new Data(TextUtils.decodeBytes(text));
+            } catch (IOException ioe) {
+                return null; // if data cannot be decoded, ignore it.
+            }
+
+        case PropertySet.PROPERTIES:
+
+            try {
+                Properties props = new Properties();
+                NodeList pElements = XMLUtils.xpathList(e, "properties/property");
+
+                for (int i = 0; i < pElements.getLength(); i++) {
+                    Element pElement = (Element) pElements.item(i);
+                    props.put(pElement.getAttribute("key"), XMLUtils.getElementText(pElement));
+                }
+
+                return props;
+            } catch (TransformerException te) {
+                return null; // could not get nodes via x-path
+            }
+
+        default:
+            return null;
+        }
+    }
+
+    /**
+    * Append <property key="..." type="...">...</property> tag to document.
+    */
+    private void saveValue(Document doc, String key, int type, Object value) {
+        Element element = doc.createElement("property");
+        element.setAttribute("key", key);
+        element.setAttribute("type", type(type));
+
+        Node valueNode;
+
+        switch (type) {
+        case PropertySet.BOOLEAN:
+        case PropertySet.INT:
+        case PropertySet.LONG:
+        case PropertySet.DOUBLE:
+        case PropertySet.STRING:
+            valueNode = doc.createTextNode(value.toString());
+
+            break;
+
+        case PropertySet.TEXT:
+            valueNode = doc.createCDATASection(value.toString());
+
+            break;
+
+        case PropertySet.DATE:
+            valueNode = doc.createTextNode(dateFormat.format((Date) value));
+
+            break;
+
+        case PropertySet.OBJECT:
+
+            try {
+                valueNode = doc.createCDATASection(TextUtils.encodeObject(value));
+            } catch (IOException ioe) {
+                return; // cannot save Object - carry on with rest of properties.
+            }
+
+            break;
+
+        case PropertySet.XML:
+
+            try {
+                valueNode = doc.createCDATASection(XMLUtils.print((Document) value));
+            } catch (IOException ioe) {
+                return; // cannot serialize XML - carry on with rest of properties.
+            }
+
+            break;
+
+        case PropertySet.DATA:
+
+            try {
+                valueNode = doc.createCDATASection(TextUtils.encodeBytes(((Data) value).getBytes()));
+            } catch (IOException ioe) {
+                return; // cannot save data - carry on with rest of properties.
+            }
+
+            break;
+
+        case PropertySet.PROPERTIES: { // scoping block
+            valueNode = doc.createElement("properties");
+
+            Properties props = (Properties) value;
+            Iterator pKeys = props.keySet().iterator();
+
+            while (pKeys.hasNext()) {
+                String pKey = (String) pKeys.next();
+                Element pElement = doc.createElement("property");
+                pElement.setAttribute("key", pKey);
+                pElement.setAttribute("type", "string");
+                pElement.appendChild(doc.createTextNode(props.getProperty(pKey)));
+                valueNode.appendChild(pElement);
+            }
+        }
+
+        break;
+
+        default:
+            return; // if type not recognised, stop now.
+        }
+
+        element.appendChild(valueNode);
+
+        doc.getDocumentElement().appendChild(element);
+    }
+}