package com.dbxml.labrador.jdbc;
/*
* The dbXML Labrador Software License, Version 1.0
*
*
* Copyright (c) 2003 The dbXML Group, L.L.C. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by The
* dbXML Group, L.L.C. (http://www.dbxml.com/)."
* Alternately, this acknowledgment may appear in the software
* itself, if and wherever such third-party acknowledgments normally
* appear.
*
* 4. The names "Labrador" and "dbXML Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* info@dbxml.com
*
* 5. Products derived from this software may not be called "Labrador",
* nor may "Labrador" appear in their name, without prior written
* permission of The dbXML Group, L.L.C..
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE DBXML GROUP, L.L.C. OR ITS
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* $Id: JDBCDiscovery.java,v 1.18 2003/10/29 20:11:16 bradford Exp $
*/
import java.sql.*;
import com.dbxml.labrador.Discovery;
import com.dbxml.labrador.broker.Broker;
import com.dbxml.labrador.configuration.Configuration;
import com.dbxml.labrador.configuration.ConfigurationCallback;
import com.dbxml.labrador.configuration.ConfigurationException;
import com.dbxml.labrador.types.Types;
import com.dbxml.labrador.types.Variant;
import com.dbxml.labrador.util.DOMHelper;
import com.dbxml.labrador.util.SimpleConfigurable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
/**
* JDBCDiscovery is a Discovery implementation for JDBC Statements. It
* actually does the work of invoking the Statements as well as managing
* pools of cached Statements.
*/
public final class JDBCDiscovery extends SimpleConfigurable implements Discovery {
private static final String[] EmptyStrings = new String[0];
private static final ParamInfo[] EmptyParams = new ParamInfo[0];
private static final boolean DoIndent = true;
private static final String IndentString = "\n ";
private static final String RS_RESULTS = "results";
private static final String RS_RESULTSET = "resultset";
private static final String RS_METADATA = "metadata";
private static final String RS_COLINFO = "colinfo";
private static final String RS_NAME = "name";
private static final String RS_TYPE = "type";
private static final String RS_ROW = "row";
private static final String RS_COL = "col";
private static final String METHOD = "method";
private static final String NAME = "name";
private static final String PARAM = "param";
private static final String TYPE = "type";
private static final String STATEMENT = "statement";
private static final String PROCEDURE = "procedure";
private static final String VOID = "void";
private static final String STRING = "string";
private static final String INT = "int";
private static final String FLOAT = "float";
private static final String BOOLEAN = "boolean";
private static final String DOCUMENT = "document";
private static final String RESULTSET = "resultset";
private static final String[] TYPES = {
VOID,
STRING,
INT,
FLOAT,
BOOLEAN,
DOCUMENT,
RESULTSET
};
private static final String[] STATEMENT_TYPES = {
VOID,
RESULTSET
};
private static final String[] PROCEDURE_TYPES = {
VOID,
STRING,
INT,
FLOAT,
BOOLEAN,
RESULTSET
};
private static final String[] PARAM_TYPES = {
STRING,
INT,
FLOAT,
BOOLEAN,
DOCUMENT
};
private static final int[] TYPE_MAPPINGS = {
Types.VOID,
Types.STRING,
Types.INT,
Types.FLOAT,
Types.BOOLEAN,
Types.NODE,
Types.NODE
};
private static final int[] SQL_MAPPINGS = {
java.sql.Types.VARCHAR, // VOID
java.sql.Types.VARCHAR, // STRING
java.sql.Types.INTEGER, // INT
java.sql.Types.DOUBLE, // FLOAT
java.sql.Types.BOOLEAN, // BOOLEAN
java.sql.Types.CLOB, // DOCUMENT
java.sql.Types.CLOB // RESULTSET
};
private Map methods = new HashMap(); // MethodInfo
private JDBCConnectionPool pool;
public JDBCDiscovery(JDBCConnectionPool pool) {
this.pool = pool;
}
public void setConfig(Configuration config) throws ConfigurationException {
super.setConfig(config);
config.processChildren(METHOD, new ConfigurationCallback() {
public void process(Configuration cfg) {
String name = cfg.getAttribute(NAME);
if ( name == null || name.length() == 0 ) {
Broker.printerr("The '"+NAME+"' attribute is required for '"+METHOD+"'");
System.exit(1);
}
boolean storedProc = false;
List params = new ArrayList();
String sql = null;
Integer resultType = null;
Configuration[] cfgs = cfg.getChildren();
for ( int i = 0; i < cfgs.length; i++ ) {
String tag = cfgs[i].getName();
if ( tag.equals(STATEMENT) || tag.equals(PROCEDURE) ) {
if ( sql != null ) {
Broker.printerr("A '"+STATEMENT+"' or '"+PROCEDURE+"' tag already defined in '"+METHOD+"'");
System.exit(1);
}
storedProc = tag.equals(PROCEDURE);
sql = cfgs[i].getValue();
String resType = cfgs[i].getAttribute(TYPE);
resultType = new Integer(getMappedType(cfgs[i].getAttribute(TYPE)));
if ( storedProc )
checkType(tag, resType, PROCEDURE_TYPES);
else
checkType(tag, resType, STATEMENT_TYPES);
}
else if ( tag.equals(PARAM) ) {
String paramName = cfgs[i].getAttribute(NAME);
String parmType = cfgs[i].getAttribute(TYPE);
int paramType = getMappedType(parmType);
checkType(PARAM, parmType, PARAM_TYPES);
ParamInfo p = new ParamInfo(paramName, paramType);
params.add(p);
}
else {
Broker.printerr("Invalid tag '"+tag+"' in '"+METHOD+"'");
System.exit(1);
}
}
if ( sql == null ) {
Broker.printerr("No '"+STATEMENT+"' or '"+PROCEDURE+"' tags defined");
System.exit(1);
}
ParamInfo[] p = (ParamInfo[])params.toArray(EmptyParams);
int res;
if ( resultType != null )
res = resultType.intValue();
else
res = Types.VOID;
MethodInfo m = new MethodInfo(name, res, p, sql, storedProc);
methods.put(name, m);
}
});
}
private static void checkType(String tag, String type, String[] types) {
if ( type == null || type.length() == 0 ) {
Broker.printerr("'"+TYPE+"' attribute for '"+tag+"' has not been set");
System.exit(1);
}
for ( int i = 0; i < types.length; i++ )
if ( types[i].equalsIgnoreCase(type) )
return;
Broker.printerr("'"+type+"' is not a valid value for the '"+tag+"' element's '"+TYPE+"' attribute");
System.exit(1);
}
private static int getMappedType(String type) {
for ( int i = 0; i < TYPES.length; i++ )
if ( TYPES[i].equalsIgnoreCase(type) )
return TYPE_MAPPINGS[i];
return Types.UNKNOWN;
}
private static int getSQLType(int type) {
for ( int i = 0; i < TYPES.length; i++ )
if ( TYPE_MAPPINGS[i] == type )
return SQL_MAPPINGS[i];
return -1;
}
public String[] listMethods() {
return (String[])methods.keySet().toArray(EmptyStrings);
}
public int getParamCount(String method) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null )
return info.params.length;
else
return 0;
}
public String[] listParams(String name) {
MethodInfo info = (MethodInfo)methods.get(name);
if ( info != null )
return info.paramNames;
else
return null;
}
public int getReturnType(String method) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null )
return info.type;
else
return Types.UNKNOWN;
}
public boolean isReturnArray(String method) {
return false; // Won't happen in JDBC-land
}
public int getParamType(String method, int index) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null && index < info.params.length )
return info.params[index].type;
else
return Types.UNKNOWN;
}
public boolean isParamArray(String method, int index) {
return false; // Won't happen in JDBC-land
}
public Object invoke(String name, Object[] params) throws Throwable {
MethodInfo method = (MethodInfo)methods.get(name);
if ( method != null )
return method.invoke(params);
else
throw new Exception("Method '" + name + "' Not Found");
}
private void indent(Document doc, Element elem, int size) {
if ( DoIndent ) {
String s = IndentString.substring(0, size+1);
elem.appendChild(doc.createTextNode(s));
}
}
private Document buildResultSet(Statement s) throws Throwable {
/** @todo Might want to modify this to return String */
Document doc = DOMHelper.newDocument();
Element rootElem = doc.createElement(RS_RESULTS);
doc.appendChild(rootElem);
do {
ResultSet rs = s.getResultSet();
Element rsElem = doc.createElement(RS_RESULTSET);
indent(doc, rootElem, 3);
rootElem.appendChild(rsElem);
indent(doc, rootElem, 0);
// Meta Data
ResultSetMetaData rsmd = rs.getMetaData();
int colCount = rsmd.getColumnCount();
Element meta = doc.createElement(RS_METADATA);
indent(doc, rsElem, 6);
rsElem.appendChild(meta);
for ( int i = 0; i < colCount; i++ ) {
Element col = doc.createElement(RS_COLINFO);
indent(doc, meta, 9);
meta.appendChild(col);
col.setAttribute(RS_NAME, rsmd.getColumnName(i+1));
col.setAttribute(RS_TYPE, rsmd.getColumnTypeName(i+1));
}
indent(doc, meta, 6);
// Rows
while ( rs.next() ) {
Element row = doc.createElement(RS_ROW);
indent(doc, rsElem, 6);
rsElem.appendChild(row);
for ( int i = 0; i < colCount; i++ ) {
Element col = doc.createElement(RS_COL);
indent(doc, row, 9);
row.appendChild(col);
Text t = doc.createTextNode(rs.getString(i+1));
col.appendChild(t);
}
indent(doc, row, 6);
}
indent(doc, rsElem, 3);
rs.close();
}
while ( s.getMoreResults() );
return doc;
}
/**
* ParamInfo encapsulates parameter type and name information
*/
private class ParamInfo {
public String name;
public int type;
public ParamInfo(String name, int type) {
this.name = name;
this.type = type;
}
}
/**
* MethodInfo encapsulates Method result, name, and parameter information
*/
private class MethodInfo extends ParamInfo {
public ParamInfo[] params;
public String[] paramNames;
public String sql;
public boolean storedProc;
public Map statements = new WeakHashMap();
public MethodInfo(String name, int type, ParamInfo[] params, String sql, boolean storedProc) {
super(name, type);
this.params = params;
this.sql = sql;
this.storedProc = storedProc;
calcParamNames();
}
public void calcParamNames() {
paramNames = new String[params.length];
for ( int i = 0; i < paramNames.length; i++ )
paramNames[i] = params[i].name;
}
private Object invoke(Variant[] args) throws Throwable {
JDBCConnection conn = null;
PreparedStatement ps = null;
try {
conn = pool.getConnection();
ps = (PreparedStatement)statements.get(conn);
if ( ps == null ) {
if ( storedProc ) {
CallableStatement cs = conn.prepareCall(sql);
if ( type != Types.VOID )
cs.registerOutParameter(1, getSQLType(type));
ps = cs;
}
else
ps = conn.prepareStatement(sql);
}
int offset;
if ( storedProc && type != Types.VOID )
offset = 2;
else
offset = 1;
for ( int i = 0; i < params.length; i++ ) {
switch ( params[i].type ) {
case Types.STRING:
ps.setString(i+offset, args[i].getString());
break;
case Types.INT:
ps.setLong(i+offset, args[i].getLong());
break;
case Types.FLOAT:
ps.setDouble(i+offset, args[i].getDouble());
break;
case Types.BOOLEAN:
ps.setBoolean(i+offset, args[i].getBoolean());
break;
case Types.NODE:
ps.setString(i+offset, args[i].getString());
break;
}
}
Object result = null;
boolean hasResults = ps.execute();
if ( storedProc ) {
CallableStatement cs = (CallableStatement)ps;
switch ( type ) {
case Types.STRING:
result = cs.getString(1);
break;
case Types.INT:
result = new Long(cs.getLong(1));
break;
case Types.FLOAT:
result = new Double(cs.getDouble(1));
break;
case Types.BOOLEAN:
result = new Boolean(cs.getBoolean(1));
break;
case Types.NODE:
result = buildResultSet(cs);
break;
}
}
else if ( type == Types.NODE && hasResults )
result = buildResultSet(ps);
return result;
}
catch ( SQLException e ) {
try {
if ( ps != null )
ps.close();
if ( conn != null )
conn.close();
}
catch ( Exception ex ) {
/** @todo Hmmmm */
}
throw e;
}
}
public Object invoke(Object[] params) throws Throwable {
Variant[] v = new Variant[params.length];
for ( int i = 0; i < v.length; i++ )
v[i] = new Variant(params[i]);
return invoke(v);
}
}
}