diff --git a/build.xml b/build.xml
index 7cf520c2..a04f95fa 100644
--- a/build.xml
+++ b/build.xml
@@ -49,6 +49,10 @@
classname="javax.xml.parsers.SAXParser" classpathref="classpath"/>
+
+
+
@@ -100,7 +104,15 @@
-
+
+
+
+
+
+
+
+
+
@@ -176,6 +188,7 @@
+
diff --git a/lib/LIBS b/lib/LIBS
index 72c972c4..52610c62 100644
--- a/lib/LIBS
+++ b/lib/LIBS
@@ -1,7 +1,8 @@
commons-collections.jar
commons-digester.jar
-commons-logging.jar
commons-io.jar
+commons-logging.jar
+ejb3-persistence.jar
mail.jar
servlet-2.3.jar
velocity-1.5-dev.jar
diff --git a/src/java/com/samskivert/jdbc/DuplicateKeyException.java b/src/java/com/samskivert/jdbc/DuplicateKeyException.java
new file mode 100644
index 00000000..ed085a15
--- /dev/null
+++ b/src/java/com/samskivert/jdbc/DuplicateKeyException.java
@@ -0,0 +1,33 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2006 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.jdbc;
+
+import com.samskivert.io.PersistenceException;
+
+/**
+ * Thrown when an insert or update results in a duplicate key on a column that
+ * has a uniqueness constraint.
+ */
+public class DuplicateKeyException extends PersistenceException
+{
+ public DuplicateKeyException (String message)
+ {
+ super(message);
+ }
+}
diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java
new file mode 100644
index 00000000..22715019
--- /dev/null
+++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java
@@ -0,0 +1,174 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2006 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.jdbc.depot;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import java.util.ArrayList;
+
+import javax.persistence.Id;
+
+/**
+ * Handles the marshalling and unmarshalling of persistent instances to JDBC
+ * primitives ({@link PreparedStatement} and {@link ResultSet}).
+ */
+public class DepotMarshaller
+{
+ /**
+ * Creates a marshaller for the specified persistent object class.
+ */
+ public DepotMarshaller (Class pclass)
+ {
+ _pclass = pclass;
+
+ // determine our table name
+ _tableName = _pclass.getName();
+ _tableName = _tableName.substring(_tableName.lastIndexOf(".")+1);
+
+ // introspect on the class and create field marshallers for all of its
+ // fields
+ ArrayList flist = new ArrayList();
+ for (Field field : _pclass.getFields()) {
+ // the field must be public, non-static and non-transient
+ int mods = field.getModifiers();
+ if (((mods & Modifier.PUBLIC) == 0) ||
+ ((mods & Modifier.STATIC) != 0) ||
+ ((mods & Modifier.TRANSIENT) != 0)) {
+ continue;
+ }
+
+ FieldMarshaller fm = FieldMarshaller.createMarshaller(field);
+ flist.add(fm);
+
+ // check to see if this is our primary key
+ for (Annotation ann : field.getDeclaredAnnotations()) {
+ System.err.println(
+ "Annotation " + ann + " (" + ann.getClass() + ")");
+ }
+ }
+ _fields = flist.toArray(new FieldMarshaller[flist.size()]);
+
+ // generate our full list of columns for use in queries
+ StringBuffer columns = new StringBuffer();
+ for (FieldMarshaller fm : _fields) {
+ if (columns.length() > 0) {
+ columns.append(",");
+ }
+ columns.append(fm.getColumnName());
+ }
+ _fullColumnList = columns.toString();
+ }
+
+ /**
+ * Returns the name of the table in which persistence instances of this
+ * class are stored. By default this is the classname of the persistent
+ * object without the package.
+ */
+ public String getTableName ()
+ {
+ return _tableName;
+ }
+
+ /**
+ * Returns the field name of the primary key for this persistent object
+ * class or null if it did not declare a primary key.
+ */
+ public String getPrimaryKey ()
+ {
+ return _primaryKey == null ? null : _primaryKey.getColumnName();
+ }
+
+ /**
+ * Creates a query for instances of this persistent object type using the
+ * supplied key.
+ */
+ public PreparedStatement createQuery (
+ Connection conn, DepotRepository.Key key)
+ throws SQLException
+ {
+ String query = "select " + _fullColumnList + " from " + getTableName() +
+ " where " + key.toWhereClause();
+ PreparedStatement pstmt = conn.prepareStatement(query);
+ for (int ii = 0; ii < key.indices.length; ii++) {
+ pstmt.setObject(ii+1, key.values[ii]);
+ }
+ return pstmt;
+ }
+
+ /**
+ * Creates a persistent object from the supplied result set.
+ */
+ public T createObject (ResultSet rs)
+ throws SQLException
+ {
+ try {
+ T po = (T)_pclass.newInstance();
+ for (int ii = 0; ii < _fields.length; ii++) {
+ _fields[ii].getValue(rs, ii+1, po);
+ }
+ return po;
+ } catch (Exception e) {
+ String errmsg = "Failed to unmarshall persistent object " +
+ "[pclass=" + _pclass.getName() + "]";
+ throw (SQLException)new SQLException(errmsg).initCause(e);
+ }
+ }
+
+ /**
+ * Creates a statement that will insert the supplied persistent object into
+ * the database.
+ */
+ public PreparedStatement createInsert (Connection conn, Object po)
+ throws SQLException
+ {
+ return null;
+ }
+
+ /**
+ * Creates a statement that will update the supplied persistent object
+ * using the supplied key.
+ */
+ public PreparedStatement createUpdate (
+ Connection conn, Object po, DepotRepository.Key key)
+ throws SQLException
+ {
+ return null;
+ }
+
+ /**
+ * Binds the specified object to the specified prepared statement.
+ */
+ public void bindObject (PreparedStatement stmt, Object po)
+ {
+ }
+
+ protected Class _pclass;
+ protected FieldMarshaller[] _fields;
+
+ protected String _tableName;
+ protected FieldMarshaller _primaryKey;
+ protected String _fullColumnList;
+}
diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java
new file mode 100644
index 00000000..3fafd03e
--- /dev/null
+++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java
@@ -0,0 +1,313 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2006 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.jdbc.depot;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+import com.samskivert.io.PersistenceException;
+import com.samskivert.jdbc.ConnectionProvider;
+
+/**
+ * Provides a base for classes that provide access to persistent objects. Also
+ * defines the mechanism by which all persistent queries and updates are routed
+ * through the distributed cache.
+ */
+public class DepotRepository
+{
+ protected DepotRepository (ConnectionProvider conprov)
+ {
+ _conprov = conprov;
+ }
+
+ protected T load (Class type, Comparable primaryKey)
+ throws PersistenceException
+ {
+ DepotMarshaller marsh = getMarshaller(type);
+ Key key = new Key(marsh.getPrimaryKey(), primaryKey);
+ return invoke(new ObjectQuery(marsh, key));
+ }
+
+ protected T load (Class type, Key key)
+ throws PersistenceException
+ {
+ DepotMarshaller marsh = getMarshaller(type);
+ return invoke(new ObjectQuery(marsh, key));
+ }
+
+ protected > Collection findAll (
+ Class type, Key key)
+ throws PersistenceException
+ {
+ DepotMarshaller marsh = getMarshaller(type);
+ return invoke(new ObjectCollectionQuery(marsh, key));
+ }
+
+ protected T findAll (
+ String index, Comparable key, CollectionQuery query)
+ throws PersistenceException
+ {
+ return null;
+ }
+
+ protected void insert (Object record)
+ throws PersistenceException
+ {
+ }
+
+ protected int update (Object record)
+ throws PersistenceException
+ {
+ return 0;
+ }
+
+ protected int update (Object record, String ... modifiedFields)
+ throws PersistenceException
+ {
+ return 0;
+ }
+
+ protected int updatePartial (Class type,
+ Comparable primaryKey, Object ... fieldsValues)
+ throws PersistenceException
+ {
+ return 0;
+ }
+
+ protected int updatePartial (Class type,
+ Key key, Object ... fieldsValues)
+ throws PersistenceException
+ {
+ return 0;
+ }
+
+ protected int updateLiteral (Class type,
+ Comparable primaryKey, Object ... fieldsValues)
+ {
+ return 0;
+ }
+
+ protected int updateLiteral (Class type,
+ Key key, Object ... fieldsValues)
+ {
+ return 0;
+ }
+
+ protected int store (Object record)
+ throws PersistenceException
+ {
+ return 0;
+ }
+
+ protected void delete (Object record)
+ throws PersistenceException
+ {
+ }
+
+ protected void delete (Class type, Comparable primaryKey)
+ throws PersistenceException
+ {
+ }
+
+ protected void deleteAll (Class type, Key key)
+ throws PersistenceException
+ {
+ }
+
+ protected DepotMarshaller getMarshaller (Class type)
+ {
+ @SuppressWarnings("unchecked")DepotMarshaller marshaller =
+ (DepotMarshaller)_marshallers.get(type);
+ if (marshaller == null) {
+ _marshallers.put(type, marshaller = new DepotMarshaller(type));
+ }
+ return marshaller;
+ }
+
+ protected T invoke (Query query)
+ throws PersistenceException
+ {
+ // TODO: check the cache using query.getKey()
+
+ // TODO: retry query on transient failure
+ Connection conn = _conprov.getConnection(getIdent(), true);
+ try {
+ return query.invoke(conn);
+
+ } catch (SQLException sqe) {
+ throw new PersistenceException("Query failure " + query, sqe);
+
+ } finally {
+ _conprov.releaseConnection(getIdent(), true, conn);
+ }
+ }
+
+ /**
+ * Returns the identifier to be used when obtaining JDBC connections from
+ * our connection provider. The default implementation uses the class name
+ * of this repository.
+ */
+ protected String getIdent ()
+ {
+ return getClass().getName();
+ }
+
+ protected static class Key
+ {
+ public String[] indices;
+ public Comparable[] values;
+
+ public Key (String[] indices, Comparable[] values)
+ {
+ this.indices = indices;
+ this.values = values;
+ }
+
+ public Key (String index, Comparable value)
+ {
+ this(new String[] { index }, new Comparable[] { value });
+ }
+
+ public String toWhereClause ()
+ {
+ StringBuffer where = new StringBuffer();
+ for (int ii = 0; ii < indices.length; ii++) {
+ if (ii > 0) {
+ where.append(" and ");
+ }
+ where.append(indices[ii]).append(" = ?");
+ }
+ return where.toString();
+ }
+ }
+
+ protected static class Key2 extends Key
+ {
+ public Key2 (String index1, Comparable value1,
+ String index2, Comparable value2)
+ {
+ super(new String[] { index1, index2 },
+ new Comparable[] { value1, value2 });
+ }
+ }
+
+ protected static abstract class Query
+ {
+ public Key getKey ()
+ {
+ return _key;
+ }
+
+ public abstract T invoke (Connection conn)
+ throws SQLException, PersistenceException;
+
+ protected Query (Key key)
+ {
+ _key = key;
+ }
+
+ protected Key _key;
+ }
+
+ protected static abstract class CollectionQuery
+ {
+ public abstract T invoke (Connection conn)
+ throws SQLException, PersistenceException;
+ }
+
+ protected static class ObjectQuery extends Query
+ {
+ public ObjectQuery (DepotMarshaller marsh, Key key) {
+ super(key);
+ _marsh = marsh;
+ }
+
+ public T invoke (Connection conn)
+ throws SQLException, PersistenceException
+ {
+ PreparedStatement stmt = _marsh.createQuery(conn, _key);
+ try {
+ T result = null;
+ ResultSet rs = stmt.executeQuery();
+ if (rs.next()) {
+ result = _marsh.createObject(rs);
+ }
+ // TODO: if (rs.next()) issue warning?
+ rs.close();
+ return result;
+
+ } finally {
+ stmt.close();
+ }
+ }
+
+ protected DepotMarshaller _marsh;
+ }
+
+ protected static class ObjectCollectionQuery
+ extends Query>
+ {
+ public ObjectCollectionQuery (DepotMarshaller marsh, Key key) {
+ super(key);
+ _marsh = marsh;
+ }
+
+ public ArrayList invoke (Connection conn)
+ throws SQLException, PersistenceException
+ {
+ PreparedStatement stmt = _marsh.createQuery(conn, _key);
+ try {
+ ArrayList results = new ArrayList();
+ ResultSet rs = stmt.executeQuery();
+ while (rs.next()) {
+ results.add(_marsh.createObject(rs));
+ }
+ return results;
+
+ } finally {
+ stmt.close();
+ }
+ }
+
+ protected DepotMarshaller _marsh;
+ }
+
+// protected static abstract class InstanceUpdate
+// {
+// public void invoke (T object)
+// throws PersistenceException;
+// }
+
+// protected static abstract class CollectionUpdate
+// {
+// public void invoke (Collection collection)
+// throws PersistenceException;
+// }
+
+ protected ConnectionProvider _conprov;
+
+ protected HashMap, DepotMarshaller>> _marshallers =
+ new HashMap, DepotMarshaller>>();
+}
diff --git a/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java
new file mode 100644
index 00000000..653c668e
--- /dev/null
+++ b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java
@@ -0,0 +1,210 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2006 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.jdbc.depot;
+
+import java.lang.reflect.Field;
+
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+
+/**
+ * Handles the marshalling and unmarshalling of a particular field of a
+ * persistent object.
+ *
+ * @see DepotMarshaller
+ */
+public abstract class FieldMarshaller
+{
+ /**
+ * Creates and returns a field marshaller for the specified field. Throws
+ * an exception if the field in question cannot be marshalled.
+ */
+ public static FieldMarshaller createMarshaller (Field field)
+ {
+ Class> ftype = field.getType();
+ FieldMarshaller marshaller;
+
+ // primitive types
+ if (ftype.equals(Boolean.TYPE)) {
+ marshaller = new PBooleanMarshaller();
+ } else if (ftype.equals(Byte.TYPE)) {
+ marshaller = new PByteMarshaller();
+ } else if (ftype.equals(Short.TYPE)) {
+ marshaller = new PShortMarshaller();
+ } else if (ftype.equals(Integer.TYPE)) {
+ marshaller = new PIntMarshaller();
+ } else if (ftype.equals(Long.TYPE)) {
+ marshaller = new PLongMarshaller();
+ } else if (ftype.equals(Float.TYPE)) {
+ marshaller = new PFloatMarshaller();
+ } else if (ftype.equals(Double.TYPE)) {
+ marshaller = new PDoubleMarshaller();
+
+ // "natural" types
+ } else if (ftype.equals(Byte.class) ||
+ ftype.equals(Short.class) ||
+ ftype.equals(Integer.class) ||
+ ftype.equals(Long.class) ||
+ ftype.equals(Float.class) ||
+ ftype.equals(Double.class) ||
+ ftype.equals(String.class)) {
+ marshaller = new ObjectMarshaller();
+
+ // TODO: byte array, (other array types?)
+
+ // SQL types
+ } else if (ftype.equals(Date.class) ||
+ ftype.equals(Time.class) ||
+ ftype.equals(Timestamp.class) ||
+ ftype.equals(Blob.class) ||
+ ftype.equals(Clob.class)) {
+ marshaller = new ObjectMarshaller();
+
+ } else {
+ throw new IllegalArgumentException(
+ "Cannot marshall field of type '" + ftype.getName() + "'.");
+ }
+
+ marshaller.init(field);
+ return marshaller;
+ }
+
+ /**
+ * Reads the value of our field from the persistent object and sets that
+ * value into the specified column of the supplied prepared statement.
+ */
+ public abstract void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException;
+
+ /**
+ * Reads the specified column from the supplied result set and writes it to
+ * the appropriate field of the persistent object.
+ */
+ public abstract void getValue (ResultSet rset, int column, Object po)
+ throws SQLException, IllegalAccessException;
+
+ /**
+ * Returns the name of the table column used to store this field.
+ */
+ public String getColumnName ()
+ {
+ return _field.getName();
+ }
+
+ protected void init (Field field)
+ {
+ _field = field;
+ }
+
+ protected static class PBooleanMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setBoolean(column, _field.getBoolean(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.setBoolean(po, rs.getBoolean(column));
+ }
+ }
+
+ protected static class PByteMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setByte(column, _field.getByte(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.setByte(po, rs.getByte(column));
+ }
+ }
+
+ protected static class PShortMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setShort(column, _field.getShort(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.setShort(po, rs.getShort(column));
+ }
+ }
+
+ protected static class PIntMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setInt(column, _field.getInt(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.setInt(po, rs.getInt(column));
+ }
+ }
+
+ protected static class PLongMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setLong(column, _field.getLong(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.setLong(po, rs.getLong(column));
+ }
+ }
+
+ protected static class PFloatMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setFloat(column, _field.getFloat(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.setFloat(po, rs.getFloat(column));
+ }
+ }
+
+ protected static class PDoubleMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setDouble(column, _field.getDouble(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.setDouble(po, rs.getDouble(column));
+ }
+ }
+
+ protected static class ObjectMarshaller extends FieldMarshaller {
+ public void setValue (Object po, PreparedStatement ps, int column)
+ throws SQLException, IllegalAccessException {
+ ps.setObject(column, _field.get(po));
+ }
+ public void getValue (ResultSet rs, int column, Object po)
+ throws SQLException, IllegalAccessException {
+ _field.set(po, rs.getObject(column));
+ }
+ }
+
+ protected Field _field;
+}