001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/datastore/xml/JDOMDocumentDriver.java,v 1.8 2003/08/14 05:46:41 wbiggs Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM 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
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm.datastore.xml;
021:
022: import java.io.IOException;
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.Iterator;
026: import java.util.Set;
027:
028: import org.xorm.datastore.Row;
029: import org.xorm.datastore.Column;
030: import org.xorm.datastore.Table;
031: import org.xorm.datastore.DataFetchGroup;
032: import org.xorm.datastore.DatastoreDriver;
033: import org.xorm.datastore.DriverException;
034: import org.xorm.query.Condition;
035: import org.xorm.query.Selector;
036: import org.xorm.query.SimpleCondition;
037: import org.xorm.util.TypeConverter;
038:
039: import org.jdom.Document;
040: import org.jdom.Element;
041:
042: /**
043: * A datastore driver that uses an XML document as the datastore.
044: * Datastore XML descriptor files need to specify a column named "."
045: * as the primary key column; table names should match element names.
046: * Data column names should be specified in a limited XPath notation.
047: * Examples are "@name", "description/text()", and ".." for a parent
048: * reference.
049: *
050: * This class relies on the transactional mechanics of the DocumentHolder
051: * class. At the beginning of each transaction, it acquires a document
052: * by calling checkout(); upon commit (but not rollback) it calls
053: * checkin().
054: *
055: * @author Wes Biggs
056: */
057: public class JDOMDocumentDriver implements DatastoreDriver {
058: private DocumentHolder documentHolder;
059: private Document document;
060: private boolean readOnly;
061: private boolean onlyDidReads;
062:
063: /**
064: * Sets the data source, which must be an instance of DocumentHolder.
065: */
066: public JDOMDocumentDriver(DocumentHolder documentHolder) {
067: this .documentHolder = documentHolder;
068: }
069:
070: /**
071: * Begins a transaction by calling checkout on the DocumentHolder.
072: */
073: public void begin(boolean readOnly) {
074: document = documentHolder.checkout();
075: this .readOnly = readOnly;
076: onlyDidReads = true;
077: }
078:
079: /**
080: * Calls checkin() on the DocumentHolder if any write operations
081: * were called during the transaction.
082: */
083: public void commit() throws DriverException {
084: if (!onlyDidReads) {
085: documentHolder.checkin(document);
086: }
087: document = null; // reacquire in next transaction
088: }
089:
090: public void rollback() {
091: document = null; // reacquire in next transaction
092: }
093:
094: // CRUD methods
095: public void create(Row row) {
096: onlyDidReads = false;
097: Table table = row.getTable();
098: Column primaryKey = table.getPrimaryKey();
099:
100: Element element = new Element(table.getName());
101: row.setValue(primaryKey, element);
102:
103: Iterator it = table.getColumns().iterator();
104: while (it.hasNext()) {
105: Column c = (Column) it.next();
106: setValue(element, c, row.getValue(c), true);
107: }
108: // If the newly created element is unattached at this point,
109: // it is not contained within any other element tag, and therefore
110: // it must be added under the document root.
111: if (element.getParent() == null && !element.isRootElement()) {
112: document.getRootElement().addContent(element);
113: }
114: }
115:
116: public void update(Row row) {
117: onlyDidReads = false;
118: Table table = row.getTable();
119: Column primaryKey = table.getPrimaryKey();
120:
121: Element element = (Element) row.getValue(primaryKey);
122: Iterator it = table.getColumns().iterator();
123: while (it.hasNext()) {
124: Column c = (Column) it.next();
125: if (row.isDirty(c)) {
126: setValue(element, c, row.getValue(c), false);
127: }
128: }
129: }
130:
131: public void delete(Row row) {
132: onlyDidReads = false;
133: Table table = row.getTable();
134: Column primaryKey = table.getPrimaryKey();
135:
136: Element element = (Element) row.getValue(primaryKey);
137: element.detach();
138: // TODO: deal with cascaded delete ramifications on the cache
139: }
140:
141: public Collection select(Selector selector, Set extraRows) {
142: Condition condition = selector.getCondition();
143: Table table = selector.getTable();
144: Collection xmlResults = null;
145: if (condition == null) {
146: xmlResults = new ArrayList();
147: xmlResults.add(deriveValue(document.getRootElement(), table
148: .getName()));
149: } else if (condition instanceof SimpleCondition) {
150: SimpleCondition sc = (SimpleCondition) condition;
151: Column column = sc.getColumn();
152: Object value = sc.getValue();
153:
154: // ".." == (Element) means get all children of an element
155: // with an element name matching the table name.
156:
157: if ("..".equals(column.getName())
158: && (value instanceof Element)) {
159: Element parent = (Element) value;
160: xmlResults = parent.getChildren(table.getName());
161: }
162: }
163:
164: // Populate the rows
165: ArrayList rows = new ArrayList();
166: if (xmlResults == null)
167: return rows; // no results
168:
169: Iterator i = xmlResults.iterator();
170: while (i.hasNext()) {
171: Element element = (Element) i.next();
172: Row row = new Row(table);
173: populate(row, element);
174: rows.add(row);
175: }
176: return rows;
177: }
178:
179: private void populate(Row row, Element element) {
180: Iterator j = row.getTable().getColumns().iterator();
181: while (j.hasNext()) {
182: Column c = (Column) j.next();
183: Object value = deriveValue(element, c.getName());
184: if (value instanceof String) {
185: // Convert to Java Type
186: Class javaType = XMLType.forName(c.getType());
187: if (javaType != null) {
188: value = TypeConverter.convertToType(value,
189: javaType, c.getFormat());
190: }
191: }
192: row.setValue(c, value);
193: }
194: }
195:
196: public int count(Selector selector) {
197: return select(selector, null).size();
198: }
199:
200: // Path corresponds to a very small subset of abbreviated XPath syntax
201: private Object deriveValue(Element element, String path) {
202: int pos = path.lastIndexOf('/');
203: if (pos != -1) {
204: element = navigateToElement(element, path, false);
205: path = path.substring(pos + 1);
206: }
207:
208: // attribute
209: if (path.startsWith("@")) {
210: return element.getAttributeValue(path.substring(1));
211: }
212:
213: // text node
214: if ("text()".equals(path)) {
215: return element.getTextTrim();
216: }
217:
218: if (".".equals(path)) {
219: return element;
220: } else if ("..".equals(path)) {
221: return element.getParent();
222: } else {
223: return element.getChild(path);
224: }
225: }
226:
227: /**
228: * Returns the Element indicated by the subpath (everything up to
229: * the last '/').
230: */
231: private Element navigateToElement(Element element, String path,
232: boolean create) {
233: int pos = path.indexOf('/');
234: String pathTail = null;
235: if (pos != -1) {
236: pathTail = path.substring(pos + 1);
237: path = path.substring(0, pos);
238: if ("..".equals(path)) {
239: element = element.getParent();
240: } else {
241: Element parent = element;
242: element = element.getChild(path);
243: if (create && (element == null)) {
244: element = new Element(path);
245: parent.addContent(element);
246: }
247: }
248: return navigateToElement(element, pathTail, create);
249: }
250: return element;
251: }
252:
253: private void setValue(Element element, Column c, Object value,
254: boolean create) {
255: String path = c.getName();
256: if (!(value instanceof Element)) {
257: // Convert value to String
258: value = TypeConverter.convertToType(value, String.class, c
259: .getFormat());
260: }
261:
262: int pos = path.lastIndexOf('/');
263: if (pos != -1) {
264: element = navigateToElement(element, path, create);
265: path = path.substring(pos + 1);
266: }
267:
268: // attribute
269: if (path.startsWith("@")) {
270: element.setAttribute(path.substring(1), (String) value);
271: } else if ("text()".equals(path)) {
272: element.setText((String) value);
273: } else if (".".equals(path)) {
274: // self-reference (primary key) -- ignore
275: } else if ("..".equals(path)) {
276: Element parent = (Element) value;
277: element.detach();
278: parent.addContent(element);
279: } else {
280: // Child reference
281: element.addContent((Element) value);
282: }
283: }
284:
285: }
|