package org.postgresql.util; import java.io.*; import java.lang.*; import java.lang.reflect.*; import java.net.*; import java.util.*; import java.sql.*; /** * This class uses PostgreSQL's object oriented features to store Java Objects.

* * It does this by mapping a Java Class name to a table in the database. Each * entry in this new table then represents a Serialized instance of this * class. As each entry has an OID (Object IDentifier), this OID can be * included in another table.

* * Serialize depends on a feature of Postgres that allows * a table to be used as a data type. However, Postgres support of * this feature is incomplete. The basic ability to create and use * a table as a field type in another table exists:
* CREATE TABLE myclass( var1 TEXT, var2 INTEGER );
* CREATE TABLE othertable( field1 TEXT, field2 myclass );
* INSERT INTO myclass VALUES ('Hello', 1);
* INSERT INTO othertable VALUES ('World', xxxx::myclass);
* where xxxx is the OID of a row in myclass
* This lets othertable reference a myclass instance but * the support to actually make any use of the myclass data type * is not there. For instance, you cannot compare the myclass field * with ANY other data type, not even with other myclass values. * Casting to and from the myclass type will also not work. * From the limited testing done, only the literal xxxx::myclass * syntax appears to work.

* * Queries like:
* SELECT othertable.field2.var1 FROM othertable;
* will not work but were suggested in the original Postgres * design documents.

* Because support is incomplete for table data types, tables * such as othertable that hold java instances should also * hold an oid field for the same java instance:
* CREATE othertable( field1 TEXT, field2 myclass, myclassOID oid);
* This oid-type field would be set with setInt() immediately after * setting the myclass-type field with setObject(). The order of these * set calls matters since the oid is not available until after * setting the object when inserting a new object. With the oid, * queries and comparisons etc. can be done to locate a myclass. * Read below about how to include an int oid field in your java class * that is updated by setObject() when it is inserted.

* * The myclass table represents a java class. This table is created * by Serialize.create(). Serialize.create() must be called before * the first use of the myclass java class in PreparedStatement.setObject() * calls. This is a one-time initialization step.

* * There are a number of limitations placed on the java class to be * used by Serialize: *

* * Suggested usage: *
    *
  1. Create your javaclass and include an int oid = 0; field. *
  2. Run Serialize.create( conn, javaclass ) to create the table for javaclass (once). *
  3. Create mytable in the database with fields like: jclassoid INTEGER, jclass JAVACLASS
    *
  4. Use a jdbc2.PreparedStatement to insert, update, or select from mytable. * Use setObject(2, jclass), followed by setInt(1, jclass.oid) to setup an insert. *
  5. Use jclass.oid and jclassoid to do queries since the jclass field cannot be used * for anything but fetching the javaclass instance with getObject("jclass"). *
* Finally, Serialize is a work in progress and being a utility class, it is not supported. * You are "on your own" if you use it. If you use it and make any enhancements, * please consider joining the email lists pgsql-jdbc@postgresql.org and pgsql-patches@postgresql.org * and contributing your additions. */ public class Serialize { // This is the connection that the instance refers to protected org.postgresql.Connection conn; // This is the table name protected String tableName; // This is the class name protected String className; // This is the Class for this serialzed object protected Class ourClass; /** * This creates an instance that can be used to serialize or deserialize * a Java object from a PostgreSQL table. */ public Serialize(org.postgresql.Connection c,String type) throws SQLException { try { conn = c; DriverManager.println("Serialize: initializing instance for type: " + type); tableName = toPostgreSQL(type); className = type; ourClass = Class.forName(className); } catch(ClassNotFoundException cnfe) { DriverManager.println("Serialize: " + className + " java class not found"); throw new PSQLException("postgresql.serial.noclass",type); } // Second check, the type must be a table boolean status = false; ResultSet rs = conn.ExecSQL("select typname from pg_type,pg_class where typname=relname and typname='" + tableName + "'"); if(rs!=null) { if(rs.next()) { status = true; DriverManager.println("Serialize: " + tableName + " table found"); } rs.close(); } // This should never occur, as org.postgresql has it's own internal checks if(!status) { DriverManager.println("Serialize: " + tableName + " table not found"); throw new PSQLException("postgresql.serial.table",type); } // Finally cache the fields within the table } /** * Constructor when Object is passed in */ public Serialize(org.postgresql.Connection c,Object o) throws SQLException { this(c, o.getClass().getName()); } /** * Constructor when Class is passed in */ public Serialize(org.postgresql.Connection c, Class cls) throws SQLException { this(c, cls.getName()); } /** * This fetches an object from a table, given it's OID * @param oid The oid of the object * @return Object relating to oid * @exception SQLException on error */ public Object fetch(int oid) throws SQLException { try { DriverManager.println("Serialize.fetch: " + "attempting to instantiate object of type: " + ourClass.getName() ); Object obj = ourClass.newInstance(); DriverManager.println("Serialize.fetch: " + "instantiated object of type: " + ourClass.getName() ); // NB: we use java.lang.reflect here to prevent confusion with // the org.postgresql.Field // used getFields to get only public fields. We have no way to set values // for other declarations. Maybe look for setFieldName() methods? java.lang.reflect.Field f[] = ourClass.getFields(); boolean hasOID=false; int oidFIELD=-1; StringBuffer sb = new StringBuffer("select"); char sep=' '; // build a select for the fields. Look for the oid field to use in the where for(int i=0;i * * If the object has an int called OID, and it is > 0, then * that value is used for the OID, and the table will be updated. * If the value of OID is 0, then a new row will be created, and the * value of OID will be set in the object. This enables an object's * value in the database to be updateable. * * If the object has no int called OID, then the object is stored. However * if the object is later retrieved, amended and stored again, it's new * state will be appended to the table, and will not overwrite the old * entries. * * @param o Object to store (must implement Serializable) * @return oid of stored object * @exception SQLException on error */ public int store(Object o) throws SQLException { try { // NB: we use java.lang.reflect here to prevent confusion with // the org.postgresql.Field // don't save private fields since we would not be able to fetch them java.lang.reflect.Field f[] = ourClass.getFields(); boolean hasOID=false; int oidFIELD=-1; boolean update=false; // Find out if we have an oid value for(int i=0;i 0; } } StringBuffer sb = new StringBuffer(update?"update "+tableName+" set":"insert into "+tableName+" "); char sep=update?' ':'('; for(int i=0;i -1) { StringBuffer buf = new StringBuffer(); StringTokenizer tok = new StringTokenizer(s, "'"); // handle quote as 1St charater if (idx > 0) buf.append(tok.nextToken()); while(tok.hasMoreTokens()) buf.append("''").append(tok.nextToken()); s = buf.toString(); } // if the string has backslashes in it escape them them as \\ if ((idx = s.indexOf("\\")) > -1) { StringBuffer buf = new StringBuffer(); StringTokenizer tok = new StringTokenizer(s, "\\"); if (idx > 0) buf.append(tok.nextToken()); while(tok.hasMoreTokens()) buf.append("\\\\").append(tok.nextToken()); s = buf.toString(); } return s; } /** * This method is not used by the driver, but it creates a table, given * a Serializable Java Object. It should be used before serializing any * objects. * @param c Connection to database * @param o Object to base table on * @exception SQLException on error */ public static void create(org.postgresql.Connection con,Object o) throws SQLException { create(con,o.getClass()); } /** * This method is not used by the driver, but it creates a table, given * a Serializable Java Object. It should be used before serializing any * objects. * @param c Connection to database * @param o Class to base table on * @exception SQLException on error */ public static void create(org.postgresql.Connection con,Class c) throws SQLException { if(c.isInterface()) throw new PSQLException("postgresql.serial.interface"); // See if the table exists String tableName = toPostgreSQL(c.getName()); ResultSet rs = con.ExecSQL("select relname from pg_class where relname = '"+tableName+"'"); if( rs.next() ) { DriverManager.println("Serialize.create: table "+tableName+" exists, skipping"); rs.close(); return; } // else table not found, so create it DriverManager.println("Serialize.create: table " + tableName + " not found, creating" ); // No entries returned, so the table doesn't exist StringBuffer sb = new StringBuffer("create table "); sb.append(tableName); char sep='('; // java.lang.reflect.Field[] fields = c.getDeclaredFields(); // Only store public fields, another limitation! java.lang.reflect.Field[] fields = c.getFields(); for(int i=0;i * * Because of this, a Class name may not have _ in the name.

* Another limitation, is that the entire class name (including packages) * cannot be longer than 32 characters (a limit forced by PostgreSQL). * * @param name Class name * @return PostgreSQL table name * @exception SQLException on error */ public static String toPostgreSQL(String name) throws SQLException { name = name.toLowerCase(); if(name.indexOf("_")>-1) throw new PSQLException("postgresql.serial.underscore"); // Postgres table names can only be 32 character long. // Reserve 1 char, so allow only up to 31 chars. // If the full class name with package is too long // then just use the class name. If the class name is // too long throw an exception. // if( name.length() > 31 ) { name = name.substring(name.lastIndexOf(".") + 1); if( name.length() >31 ) throw new PSQLException("postgresql.serial.namelength",name,new Integer(name.length())); } return name.replace('.','_'); } /** * This converts a org.postgresql table to a Java Class name, by replacing _ with * .

* * @param name PostgreSQL table name * @return Class name * @exception SQLException on error */ public static String toClassName(String name) throws SQLException { name = name.toLowerCase(); return name.replace('_','.'); } }