001: package org.mandarax.xkb.framework;
002:
003: /**
004: * Copyright (C) 1999-2004 Jens Dietrich (mailto:mandarax@jbdietrich.com)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: import java.beans.BeanInfo;
022: import java.beans.Introspector;
023: import java.beans.PropertyDescriptor;
024: import java.lang.reflect.Method;
025: import java.util.Date;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029:
030: import org.jdom.Element;
031: import org.mandarax.kernel.LogicFactory;
032: import org.mandarax.xkb.XKBException;
033:
034: /**
035: * An adapter class for objects. Strings and primitives are just stringified,
036: * dates represented by the time as long, classes by the name, and
037: * beans are handeled via introspection. <p>
038: * <strong>Warning:</strong> We cannot and do not solve the more general problem of XML object serialization here.
039: * Instead, this is a 80-20 solution that might work fine for simple object models. For more sophisticated
040: * object models, you will need to implement your own object adapter. In particular, we do not handle circular
041: * references.
042: * <p>
043: * In the future (once JDK 1.4 is the common platform) we will integrate the new XML serialization mechanism!
044: * @author <A href="http://www-ist.massey.ac.nz/JBDietrich" target="_top">Jens Dietrich</A>
045: * @version 3.4 <7 March 05>
046: * @since 1.6
047: * @deprecated from v 3.4 - support for new features such as validation will not be added to XKB, please use ZKB instead
048: */
049: public class XMLAdapter4Objects extends CachedXMLAdapter {
050: public static final String OBJECT = "object";
051: public static final String DATA = "data";
052: public static final String OBJECT_TYPE = "object_type";
053: public static final String STRING = String.class.getName();
054: public static final String INTEGER = Integer.class.getName();
055: public static final String BOOLEAN = Boolean.class.getName();
056: public static final String LONG = Long.class.getName();
057: public static final String SHORT = Short.class.getName();
058: public static final String CHARACTER = Character.class.getName();
059: public static final String BYTE = Byte.class.getName();
060: public static final String DOUBLE = Double.class.getName();
061: public static final String FLOAT = Float.class.getName();
062: public static final String BEAN = "bean";
063: public static final String PRIMITIVE = "primitive";
064: public static final String DATE = "date";
065: public static final String NULL = "null";
066: public static final String PROPERTIES = "properties";
067: public static final String PROPERTY_NAME = "property_name";
068: public static final String BEAN_CLASS = "bean_class";
069: public static final String CLASS = "class";
070:
071: /**
072: * Export an object, i.e., convert it to an element in the DOM.
073: * @param obj an object
074: * @param driver the generic driver
075: * @param cache a cache used in order to associate the same
076: * id with various occurences of the same object
077: * @exception an XKBException is thrown if export fails
078: */
079: protected Element _exportObject(Object obj, GenericDriver driver,
080: Map cache) throws XKBException {
081:
082: boolean debug = LOG_XKB.isDebugEnabled();
083: Element e = new Element(OBJECT);
084: // export null, primitives and strings first
085: if (obj == null) {
086: e.setAttribute(OBJECT_TYPE, NULL);
087: return e;
088: }
089: if (isSimpleObject(obj)) {
090: e.setAttribute(DATA, obj.toString());
091: e.setAttribute(OBJECT_TYPE, PRIMITIVE);
092: e.setAttribute(CLASS, obj.getClass().getName());
093: if (debug)
094: LOG_XKB.debug("Exporting object as primitive - value: "
095: + obj.toString());
096: return e;
097: }
098: // export classes
099: if (obj instanceof Class) {
100: e.setAttribute(DATA, ((Class) obj).getName());
101: e.setAttribute(OBJECT_TYPE, CLASS);
102: if (debug)
103: LOG_XKB.debug("Exporting object as class - value: "
104: + ((Class) obj).getName());
105: return e;
106: }
107: // export dates
108: if (obj instanceof Date) {
109: Date date = (Date) obj;
110: e.setAttribute(DATA, String.valueOf(date.getTime()));
111: e.setAttribute(OBJECT_TYPE, DATE);
112: e.setAttribute(CLASS, obj.getClass().getName());
113: if (debug)
114: LOG_XKB.debug("Exporting object as date - value: "
115: + date.getTime());
116: return e;
117: }
118: // otherwise export object as a bean using introspection
119: e.setAttribute(OBJECT_TYPE, BEAN);
120: e.setAttribute(CLASS, obj.getClass().getName());
121: try {
122: BeanInfo beanInfo = Introspector.getBeanInfo(
123: obj.getClass(), Object.class);
124: PropertyDescriptor[] properties = beanInfo
125: .getPropertyDescriptors();
126: Element eProperties = new Element(PROPERTIES);
127: if (debug)
128: LOG_XKB.debug("Exporting object as bean - class: "
129: + obj.getClass());
130: for (int i = 0; i < properties.length; i++) {
131: PropertyDescriptor property = properties[i];
132: String name = property.getName();
133: Method getter = property.getReadMethod();
134: Object[] par = new Object[0];
135: try {
136: Object value = getter.invoke(obj, par);
137: if (debug)
138: LOG_XKB.debug("Exporting property : " + name
139: + " -> " + value);
140: Element eValue = exportObject(value, driver, cache);
141: eValue.setAttribute(PROPERTY_NAME, name);
142: eProperties.addContent(eValue);
143: } catch (Exception x) {
144: // note: we carry on without this property but log the error !
145: LOG_XKB.error("Cannot write property " + name
146: + " for object " + obj, x);
147: }
148: }
149: e.addContent(eProperties);
150: } catch (Exception x) {
151: throw new XKBException(
152: "Export failed: cannot inspect object " + obj);
153: }
154: return e;
155:
156: }
157:
158: /**
159: * Build an object from an XML element.
160: * @param e an element
161: * @param driver the generic driver
162: * @param cache a cache used to identify objects that have the same id
163: * @param lfactory the logic factory used to create objects
164: * @exception an XKBException is thrown if export fails
165: */
166: protected Object _importObject(Element e, GenericDriver driver,
167: Map cache, LogicFactory lfactory) throws XKBException {
168: // import null and primitives first
169: String objType = e.getAttributeValue(OBJECT_TYPE);
170: if (NULL.equals(objType))
171: return null;
172: String className = e.getAttributeValue(CLASS);
173: String stringified = e.getAttributeValue(DATA);
174: if (PRIMITIVE.equals(objType)) {
175: if (STRING.equals(className))
176: return stringified;
177: if (BOOLEAN.equals(className))
178: return Boolean.valueOf(stringified);
179: if (CHARACTER.equals(className))
180: return new Character(stringified.charAt(0));
181: // the numerical types
182: try {
183: if (INTEGER.equals(className))
184: return Integer.valueOf(stringified);
185: if (BYTE.equals(className))
186: return Byte.valueOf(stringified);
187: if (SHORT.equals(className))
188: return Short.valueOf(stringified);
189: if (LONG.equals(className))
190: return Long.valueOf(stringified);
191: if (DOUBLE.equals(className))
192: return Double.valueOf(stringified);
193: if (FLOAT.equals(className))
194: return Float.valueOf(stringified);
195: } catch (NumberFormatException x) {
196: throw new XKBException("Cannot parse " + stringified
197: + " to instanciate " + objType);
198: }
199: }
200: // import classes
201: if (CLASS.equals(objType)) {
202: String clazzName = e.getAttributeValue(DATA);
203: try {
204: return Class.forName(clazzName);
205: } catch (Exception x) {
206: throw new XKBException("Cannot find class named "
207: + clazzName);
208: }
209: }
210: // import dates
211: if (DATE.equals(objType)) {
212: long time = 0;
213: try {
214: time = Long.parseLong(e.getAttributeValue(DATA));
215: } catch (NumberFormatException x) {
216: throw new XKBException("Cannot interprete " + time
217: + " as a date (cannot parse long)");
218: }
219: if (className == null)
220: return new Date(time);
221: if (java.sql.Date.class.getName().equals(className))
222: return new java.sql.Date(time);
223: if (java.sql.Time.class.getName().equals(className))
224: return new java.sql.Time(time);
225: if (java.sql.Timestamp.class.getName().equals(className))
226: return new java.sql.Timestamp(time);
227:
228: LOG_XKB.warn("Don't know implementation class " + className
229: + " for date, use java.util.Date");
230: return new Date(time);
231: }
232: // import beans
233: if (BEAN.equals(objType)) {
234: Object bean = null;
235: try {
236: Class clazz = Class.forName(className);
237: bean = clazz.newInstance();
238: BeanInfo beanInfo = Introspector.getBeanInfo(clazz,
239: Object.class);
240: PropertyDescriptor[] properties = beanInfo
241: .getPropertyDescriptors();
242: List eProperties = e.getChild(PROPERTIES).getChildren();
243: PropertyDescriptor property = null;
244: Object[] par = new Object[1];
245: for (Iterator it = eProperties.iterator(); it.hasNext();) {
246: property = null;
247: Element eProperty = (Element) it.next();
248: String name = eProperty
249: .getAttributeValue(PROPERTY_NAME);
250: // look up properties
251: for (int i = 0; i < properties.length; i++) {
252: if (properties[i].getName().equals(name))
253: property = properties[i];
254: }
255: // if no property has been found, warn but continue
256: if (property == null)
257: LOG_XKB
258: .warn("Introspection problem - cannot locate property "
259: + name + " in class " + clazz);
260: else {
261: try {
262: Method setter = property.getWriteMethod();
263: par[0] = importObject(eProperty, driver,
264: cache, lfactory);
265: setter.invoke(bean, par);
266: } catch (Exception x) {
267: // note: we carry on without this property but log the error !
268: LOG_XKB.error("Cannot set property " + name
269: + " for object " + bean, x);
270: }
271: }
272: }
273: return bean;
274: } catch (Exception x) {
275: LOG_XKB.error("Cannot import bean of class "
276: + className + " from data " + bean, x);
277: throw new XKBException("Cannot import object " + bean);
278: }
279: } else
280: throw new XKBException(
281: "Cannot import object from "
282: + e.toString()
283: + " - it is neither marked as a primitive nor as a bean");
284: }
285:
286: /**
287: * Get the name of the associated tag (element).
288: * @return a string
289: */
290: public String getTagName() {
291: return OBJECT;
292: }
293:
294: /**
295: * Get the kind of object the adapter can export/import.
296: * @return a string
297: */
298: public String getKindOfObject() {
299: return GenericDriver.OBJECT;
300: }
301:
302: /**
303: * Indicates whether the object represents a primitive or string.
304: * @return a boolean
305: * @param obj an object
306: */
307: private boolean isSimpleObject(Object obj) {
308: return (obj instanceof String) || (obj instanceof Number)
309: || (obj instanceof Boolean);
310: }
311: }
|