/*
* Jython Database Specification API 2.0
*
*
* Copyright (c) 2001 brian zimmer <bzimmer@ziclix.com>
*
*/
package com.ziclix.python.sql;
import org.python.core.ClassDictInit;
import org.python.core.Options;
import org.python.core.Py;
import org.python.core.PyArray;
import org.python.core.PyBuiltinFunctionSet;
import org.python.core.PyDictionary;
import org.python.core.PyException;
import org.python.core.PyInteger;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PyStringMap;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
/**
* Creates database connections.
* <p/>
* <pre>
* from com.ziclix.python.sql import zxJDBC
* db = zxJDBC.connect("jdbc:mysql://localhost:3306/MySql", None, None, "org.gjt.mm.mysql.Driver")
* </pre>
*
* @author brian zimmer
*/
public class zxJDBC extends PyObject implements ClassDictInit {
/**
* Field Error
*/
public static PyObject Error = Py.None;
/**
* Field Warning
*/
public static PyObject Warning = Py.None;
/**
* Field InterfaceError
*/
public static PyObject InterfaceError = Py.None;
/**
* Field DatabaseError
*/
public static PyObject DatabaseError = Py.None;
/**
* Field InternalError
*/
public static PyObject InternalError = Py.None;
/**
* Field OperationalError
*/
public static PyObject OperationalError = Py.None;
/**
* Field ProgrammingError
*/
public static PyObject ProgrammingError = Py.None;
/**
* Field IntegrityError
*/
public static PyObject IntegrityError = Py.None;
/**
* Field DataError
*/
public static PyObject DataError = Py.None;
/**
* Field NotSupportedError
*/
public static PyObject NotSupportedError = Py.None;
/**
* The ResourceBundle with error messages and doc strings
*/
private static ResourceBundle resourceBundle = null;
/**
* Instance used to create date-like objects as per the API
*/
public static DateFactory datefactory = new JavaDateFactory();
static {
try {
resourceBundle =
ResourceBundle.getBundle("com.ziclix.python.sql.resource.zxJDBCMessages");
} catch (MissingResourceException e) {
throw new RuntimeException("missing zxjdbc resource bundle");
}
}
/**
* Initializes the module.
*
* @param dict
*/
public static void classDictInit(PyObject dict) {
dict.__setitem__("apilevel", new PyString("2.0"));
dict.__setitem__("threadsafety", new PyInteger(1));
dict.__setitem__("paramstyle", new PyString("qmark"));
dict.__setitem__("__version__", Py.newString("7290"));
dict.__setitem__("Date", new zxJDBCFunc("Date", 1, 3, 3,
"construct a Date from year, month, day"));
dict.__setitem__("Time", new zxJDBCFunc("Time", 2, 3, 3,
"construct a Date from hour, minute, second"));
dict.__setitem__("Timestamp",
new zxJDBCFunc("Timestamp", 3, 6, 6,
"construct a Timestamp from year, month, day, hour, "
+ "minute, second"));
dict.__setitem__("DateFromTicks",
new zxJDBCFunc("DateFromTicks", 4, 1, 1,
"construct a Date from seconds since the epoch"));
dict.__setitem__("TimeFromTicks",
new zxJDBCFunc("TimeFromTicks", 5, 1, 1,
"construct a Time from seconds since the epoch"));
dict.__setitem__("TimestampFromTicks",
new zxJDBCFunc("TimestampFromTicks", 6, 1, 1,
"construct a Timestamp from seconds since the epoch"));
dict.__setitem__("Binary",
new zxJDBCFunc("Binary", 7, 1, 1,
"construct an object capable of holding binary data"));
zxJDBC._addSqlTypes(dict);
zxJDBC._addConnectors(dict);
zxJDBC._buildExceptions(dict);
// hide from python
dict.__setitem__("initModule", null);
dict.__setitem__("toString", null);
dict.__setitem__("getPyClass", null);
dict.__setitem__("classDictInit", null);
dict.__setitem__("_addSqlTypes", null);
dict.__setitem__("_addConnectors", null);
dict.__setitem__("_buildExceptions", null);
dict.__setitem__("buildClass", null);
dict.__setitem__("createExceptionMessage", null);
dict.__setitem__("resourceBundle", null);
dict.__setitem__("getString", null);
dict.__setitem__("makeException", null);
}
/**
* Add the types from java.sql.Types
*
* @param dict
* @throws PyException
*/
protected static void _addSqlTypes(PyObject dict) throws PyException {
PyDictionary sqltype = new PyDictionary();
dict.__setitem__("sqltype", sqltype);
try {
Class<?> c = Class.forName("java.sql.Types");
Field[] fields = c.getFields();
for (Field f : fields) {
PyString name = Py.newString(f.getName());
PyObject value = new DBApiType(f.getInt(c));
dict.__setitem__(name, value);
sqltype.__setitem__(value, name);
}
c = Class.forName("java.sql.ResultSet");
fields = c.getFields();
for (Field f : fields) {
PyString name = Py.newString(f.getName());
PyObject value = Py.newInteger(f.getInt(c));
dict.__setitem__(name, value);
}
} catch (Throwable t) {
throw makeException(t);
}
dict.__setitem__("ROWID", dict.__getitem__(Py.newString("OTHER")));
dict.__setitem__("NUMBER", dict.__getitem__(Py.newString("NUMERIC")));
dict.__setitem__("STRING", dict.__getitem__(Py.newString("VARCHAR")));
dict.__setitem__("DATETIME", dict.__getitem__(Py.newString("TIMESTAMP")));
}
/**
* Add all the possible connectors
*
* @param dict
* @throws PyException
*/
protected static void _addConnectors(PyObject dict) throws PyException {
PyObject connector = Py.None;
Properties props = new Properties();
props.put("connect", "com.ziclix.python.sql.connect.Connect");
props.put("lookup", "com.ziclix.python.sql.connect.Lookup");
props.put("connectx", "com.ziclix.python.sql.connect.Connectx");
Enumeration<?> names = props.propertyNames();
while (names.hasMoreElements()) {
String name = ((String) names.nextElement()).trim();
String className = props.getProperty(name).trim();
try {
connector = (PyObject) Class.forName(className).newInstance();
dict.__setitem__(name, connector);
Py.writeComment("zxJDBC", "loaded connector [" + className + "] as [" + name
+ "]");
} catch (Throwable t) {
Py.writeComment("zxJDBC", "failed to load connector [" + name
+ "] using class [" + className + "]");
}
}
}
/**
* Create the exception classes and get their descriptions from the resource bundle.
*
* @param dict
*/
protected static void _buildExceptions(PyObject dict) {
Error = buildClass("Error", Py.StandardError);
Warning = buildClass("Warning", Py.StandardError);
InterfaceError = buildClass("InterfaceError", Error);
DatabaseError = buildClass("DatabaseError", Error);
InternalError = buildClass("InternalError", DatabaseError);
OperationalError = buildClass("OperationalError", DatabaseError);
ProgrammingError = buildClass("ProgrammingError", DatabaseError);
IntegrityError = buildClass("IntegrityError", DatabaseError);
DataError = buildClass("DataError", DatabaseError);
NotSupportedError = buildClass("NotSupportedError", DatabaseError);
}
/**
* Return the string associated with the key for the default resource bundle. It
* first checks for 'key.N' where N starts at 0 and increments by one. If any indexed
* key is found, the results of all the indexed values are concatenated with the line
* separator. If no indexed key is found, it defaults to checking the bundle by the
* key value alone.
*
* @param key
* @return String
*/
public static String getString(String key) {
int i = 0;
List<String> lines = null;
String resource = null;
while (true) {
try {
resource = resourceBundle.getString(key + "." + (i++));
if (lines == null) {
lines = new ArrayList<String>();
}
lines.add(resource);
} catch (MissingResourceException e) {
break;
}
}
if (lines == null || lines.size() == 0) {
try {
resource = resourceBundle.getString(key);
} catch (MissingResourceException e) {
return key;
}
} else {
String sep = System.getProperty("line.separator");
StringBuffer sb = new StringBuffer();
for (i = 0; i < lines.size() - 1; i++) {
sb.append(lines.get(i)).append(sep);
}
sb.append(lines.get(lines.size() - 1));
resource = sb.toString();
}
return resource;
}
/**
* Return a formatted string. The key is used to get the format and the values
* are passed, along with the format, to a MessageFormat who formats it appropriately.
*
* @param key
* @param values
* @return String
*/
public static String getString(String key, Object[] values) {
String format = getString(key);
return MessageFormat.format(format, values);
}
/**
* Return a newly instantiated PyException of the type Error.
*
* @param msg
* @return PyException
*/
public static PyException makeException(String msg) {
return makeException(Error, msg);
}
/**
* Return a newly instantiated PyException of the given type.
*
* @param type
* @param msg
* @return PyException
*/
public static PyException makeException(PyObject type, String msg) {
return Py.makeException(type, msg == null ? Py.EmptyString : Py.newString(msg));
}
/**
* Return a newly instantiated PyException of the type Error.
*
* @param throwable
* @return PyException
*/
public static PyException makeException(Throwable throwable) {
PyObject type = Error;
if (throwable instanceof SQLException) {
String state = ((SQLException)throwable).getSQLState();
// The SQL standard is not freely available, but
// http://www.postgresql.org/docs/current/static/errcodes-appendix.html
// contains most of the SQLSTATES codes.
// Otherwise, the state is not following the standard.
if (state != null && state.length() == 5) {
if (state.startsWith("23")) {
// Class 23 => Integrity Constraint Violation
type = IntegrityError;
} else if (state.equals("40002")) {
// 40002 => TRANSACTION INTEGRITY CONSTRAINT VIOLATION
type = IntegrityError;
}
}
}
return makeException(type, throwable);
}
/**
* Return a newly instantiated PyException of the given type.
*
* @param type
* @param t
* @return PyException
*/
public static PyException makeException(PyObject type, Throwable t) {
return makeException(type, t, -1);
}
/**
* Return a newly instantiated PyException of the given type.
*
* @param type
* @param t
* @param rowIndex Row index where the error has happened. Useful for diagnosing.
* @return PyException
*/
public static PyException makeException(PyObject type, Throwable t, int rowIndex) {
if (Options.showJavaExceptions) {
CharArrayWriter buf = new CharArrayWriter();
PrintWriter writer = new PrintWriter(buf);
writer.println("Java Traceback:");
if (t instanceof PyException) {
((PyException) t).super__printStackTrace(writer);
} else {
t.printStackTrace(writer);
}
Py.stderr.print(buf.toString());
}
if (t instanceof PyException) {
return (PyException) t;
} else if (t instanceof SQLException) {
SQLException sqlException = (SQLException) t;
StringBuffer buffer = new StringBuffer();
do {
buffer.append(sqlException.getMessage());
buffer.append(" [SQLCode: " + sqlException.getErrorCode() + "]");
if (sqlException.getSQLState() != null) {
buffer.append(", [SQLState: " + sqlException.getSQLState() + "]");
}
if (rowIndex >= 0) {
buffer.append(", [Row number: " + rowIndex + "]");
}
sqlException = sqlException.getNextException();
if (sqlException != null) {
buffer.append(System.getProperty("line.separator"));
}
} while (sqlException != null);
return makeException(type, buffer.toString());
} else {
return makeException(type, t.getMessage());
}
}
/**
* Method buildClass
*
* @param classname
* @param superclass
* @param classCodeName
* @return PyObject
*/
protected static PyObject buildClass(String classname, PyObject superclass) {
PyObject dict = new PyStringMap();
dict.__setitem__("__doc__", Py.newString(getString(classname)));
dict.__setitem__("__module__", Py.newString("zxJDBC"));
return Py.makeClass(classname, superclass, dict);
}
}
class zxJDBCFunc extends PyBuiltinFunctionSet {
zxJDBCFunc(String name, int index, int minargs, int maxargs, String doc) {
super(name, index, minargs, maxargs, doc);
}
@Override
public PyObject __call__(PyObject arg) {
long ticks;
switch (index) {
case 4:
ticks = ((Number) arg.__tojava__(Number.class)).longValue();
return zxJDBC.datefactory.DateFromTicks(ticks);
case 5:
ticks = ((Number) arg.__tojava__(Number.class)).longValue();
return zxJDBC.datefactory.TimeFromTicks(ticks);
case 6:
ticks = ((Number) arg.__tojava__(Number.class)).longValue();
return zxJDBC.datefactory.TimestampFromTicks(ticks);
case 7:
if (arg instanceof PyString) {
arg = PyArray.TYPE.__call__(Py.newString("b"), arg);
}
return arg;
default :
throw info.unexpectedCall(1, false);
}
}
@Override
public PyObject __call__(PyObject arga, PyObject argb, PyObject argc) {
switch (index) {
case 1:
int year = ((Number) arga.__tojava__(Number.class)).intValue();
int month = ((Number) argb.__tojava__(Number.class)).intValue();
int day = ((Number) argc.__tojava__(Number.class)).intValue();
return zxJDBC.datefactory.Date(year, month, day);
case 2:
int hour = ((Number) arga.__tojava__(Number.class)).intValue();
int minute = ((Number) argb.__tojava__(Number.class)).intValue();
int second = ((Number) argc.__tojava__(Number.class)).intValue();
return zxJDBC.datefactory.Time(hour, minute, second);
default :
throw info.unexpectedCall(3, false);
}
}
@Override
public PyObject fancyCall(PyObject[] args) {
switch (index) {
case 3:
int year = ((Number) args[0].__tojava__(Number.class)).intValue();
int month = ((Number) args[1].__tojava__(Number.class)).intValue();
int day = ((Number) args[2].__tojava__(Number.class)).intValue();
int hour = ((Number) args[3].__tojava__(Number.class)).intValue();
int minute = ((Number) args[4].__tojava__(Number.class)).intValue();
int second = ((Number) args[5].__tojava__(Number.class)).intValue();
return zxJDBC.datefactory.Timestamp(year, month, day, hour, minute, second);
default :
throw info.unexpectedCall(args.length, false);
}
}
}