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 0000000..2271501 --- /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 0000000..3fafd03 --- /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 0000000..653c668 --- /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; +}