blob: d77899fb9f4276d9286d14b309612c762643f97b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ode.bpel.extvar.jdbc;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.swing.text.StyleContext.SmallAttributeSet;
import javax.xml.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.ode.bpel.extvar.jdbc.DbExternalVariable.Column;
import org.apache.ode.bpel.extvar.jdbc.DbExternalVariable.RowKey;
import org.apache.ode.bpel.extvar.jdbc.DbExternalVariable.RowVal;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.ObjectPrinter;
import org.apache.ode.bpel.evar.ExternalVariableModule;
import org.apache.ode.bpel.evar.ExternalVariableModuleException;
import org.apache.ode.bpel.evar.IncompleteKeyException;
import org.w3c.dom.Element;
public class JdbcExternalVariableModule implements ExternalVariableModule {
private static final Logger __log = LoggerFactory.getLogger(JdbcExternalVariableModule.class);
public static final String JDBC_NS = "http://ode.apache.org/externalVariables/jdbc";
/** Unique QName for the engine, this should be the element used for the external-variable configuration. */
public static final QName NAME = new QName(JDBC_NS, "jdbc");
/** Manually configured data sources. */
private final HashMap<String, DataSource> _dataSources = new HashMap<String, DataSource>();
/** Variables we know about via configure() method calls. */
private final HashMap<EVarId, DbExternalVariable> _vars = new HashMap<EVarId, DbExternalVariable>();
public void configure(QName pid, String extVarId, Element config) throws ExternalVariableModuleException {
EVarId evarId = new EVarId(pid, extVarId);
DataSource ds = null;
Element jndiDs = DOMUtils.findChildByName(config, new QName(JDBC_NS, "datasource-jndi"));
Element jndiRef = DOMUtils.findChildByName(config, new QName(JDBC_NS, "datasource-ref"));
Element initMode = DOMUtils.findChildByName(config, new QName(JDBC_NS, "init-mode"));
if (jndiRef != null) {
String refname = jndiRef.getTextContent().trim();
ds = _dataSources.get(refname);
if (ds == null)
throw new ExternalVariableModuleException("Data source reference \"" + refname
+ "\" not found for external variable " + evarId
+ "; make sure to register the data source with the engine!");
} else if (jndiDs != null) {
String name = jndiDs.getTextContent().trim();
Object dsCandidate;
InitialContext ctx;
try {
ctx = new InitialContext();
} catch (Exception ex) {
throw new ExternalVariableModuleException("Unable to access JNDI context for external variable " + evarId, ex);
}
try {
dsCandidate = ctx.lookup(name);
} catch (Exception ex) {
throw new ExternalVariableModuleException("Lookup of data source for " + evarId + " failed.", ex);
} finally {
try {
ctx.close();
} catch (NamingException e) { /* ignore */ }
}
if (dsCandidate == null)
throw new ExternalVariableModuleException("Data source \"" + name + "\" not found in JNDI!");
if (!(dsCandidate instanceof DataSource))
throw new ExternalVariableModuleException("JNDI object \"" + name + "\" does not implement javax.sql.DataSource");
ds = (DataSource) dsCandidate;
}
if (ds == null) {
throw new ExternalVariableModuleException("No valid data source configuration for JDBC external varible " + evarId);
}
Connection conn = null;
DatabaseMetaData metaData;
try {
conn = ds.getConnection();
metaData = conn.getMetaData();
} catch (Exception ex) {
try {
if (conn != null) conn.close();
} catch (SQLException e) {
// ignore
}
throw new ExternalVariableModuleException("Unable to open database connection for external variable " + evarId, ex);
}
try {
DbExternalVariable dbev = new DbExternalVariable(evarId, ds);
if (initMode != null)
try {
dbev._initType = InitType.valueOf(initMode.getTextContent().trim());
} catch (Exception ex) {
throw new ExternalVariableModuleException("Invalid <init-mode> value: " + initMode.getTextContent().trim());
}
Element tableName = DOMUtils.findChildByName(config, new QName(JDBC_NS, "table"));
if (tableName == null || tableName.getTextContent().trim().equals(""))
throw new ExternalVariableModuleException("Must specify <table> for external variable " + evarId);
String table = tableName.getTextContent().trim();
String schema = null;
if (table.indexOf('.') != -1) {
schema = table.substring(0, table.indexOf('.'));
table = table.substring(table.indexOf('.') + 1);
}
if (metaData.storesLowerCaseIdentifiers()) {
table = table.toLowerCase();
if (schema != null)
schema = table.toLowerCase();
} else if (metaData.storesUpperCaseIdentifiers()) {
table = table.toUpperCase();
if (schema != null)
schema = schema.toUpperCase();
}
dbev.generatedKeys = metaData.supportsGetGeneratedKeys();
ResultSet tables = metaData.getTables(null, schema, table, null);
if (tables.next()) {
dbev.table = tables.getString("TABLE_NAME");
dbev.schema = tables.getString("TABLE_SCHEM");
} else
throw new ExternalVariableModuleException("Table \"" + table + "\" not found in database.");
tables.close();
List<Element> columns = DOMUtils.findChildrenByName(config, new QName(JDBC_NS, "column"));
for (Element col : columns) {
String name = col.getAttribute("name");
String colname = col.getAttribute("column-name");
String key = col.getAttribute("key");
String gentype = col.getAttribute("generator");
String expression = col.getAttribute("expression");
if (key == null || "".equals(key))
key = "no";
if (gentype == null || "".equals(gentype))
gentype = GenType.none.toString();
if (colname == null || "".equals(colname))
colname = name;
if (name == null || "".equals(name))
throw new ExternalVariableModuleException("External variable " + evarId
+ " <column> element must have \"name\" attribute. ");
if (metaData.storesLowerCaseIdentifiers())
colname = colname.toLowerCase();
else if (metaData.storesUpperCaseIdentifiers())
colname = colname.toUpperCase();
GenType gtype;
try {
gtype = GenType.valueOf(gentype);
} catch (Exception ex) {
throw new ExternalVariableModuleException("External variable " + evarId + " column \"" + name
+ "\" generator type \"" + gentype + "\" is unknown.");
}
if (gtype == GenType.expression && (expression == null || "".equals(expression)))
throw new ExternalVariableModuleException("External variable " + evarId + " column \"" + name
+ "\" used \"expression\" generator, but did not specify an expression");
Column c = dbev.new Column(name, colname, key.equalsIgnoreCase("yes"), gtype, expression);
ResultSet cmd = metaData.getColumns(null, dbev.schema, dbev.table, colname);
try {
if (cmd.next()) {
c.dataType = cmd.getInt("DATA_TYPE");
c.nullok = cmd.getInt("NULLABLE") != 0;
} else
throw new ExternalVariableModuleException("External variable " + evarId + " referenced "
+ "non-existant column \"" + colname + "\"!");
} finally {
cmd.close();
}
dbev.addColumn(c);
}
if (dbev.numColumns() == 0)
throw new ExternalVariableModuleException("External variable " + evarId + " did not have any <column> elements!");
_vars.put(evarId, dbev);
} catch (SQLException se) {
throw new ExternalVariableModuleException("SQL Error", se);
} finally {
try {
conn.close();
} catch (SQLException e) {
}
}
}
public QName getName() {
return NAME;
}
public boolean isTransactional() {
return true;
}
public void shutdown() {
}
public void start() {
}
public void stop() {
}
public Value writeValue(QName varType, Value newval) throws ExternalVariableModuleException {
EVarId evarId = new EVarId(newval.locator.pid, newval.locator.varId);
DbExternalVariable evar = _vars.get(evarId);
if (evar == null)
throw new ExternalVariableModuleException("No such variable. "); // todo
RowKey key = evar.keyFromLocator(newval.locator);
RowVal val = evar.parseXmlRow(evar.new RowVal(), (Element) newval.value);
if (__log.isDebugEnabled())
__log.debug("JdbcExternalVariable.writeValue() RowKey: " + key + " RowVal: " + val);
if (!key.missingValues() && evar._initType == InitType.delete_insert) {
// do delete...
throw new ExternalVariableModuleException("Delete not implemented. "); // todo
}
// should we try an update first? to do this we need to have all the required keys
// and there should be some keys
boolean tryupdatefirst = (evar._initType == InitType.update || evar._initType == InitType.update_insert)
&& !evar._keycolumns.isEmpty() && !key.missingDatabaseGeneratedValues();
boolean insert = evar._initType != InitType.update;
if (__log.isDebugEnabled())
__log.debug("tryUpdateFirst: " + tryupdatefirst
+ " insert: " + insert
+ " initType: " + evar._initType
+ " key.isEmpty: " + evar._keycolumns.isEmpty()
+ " key.missingValues: " + key.missingValues()
+ " key.missingDBValues: " + key.missingDatabaseGeneratedValues());
try {
if (tryupdatefirst)
insert = execUpdate(evar, key, val) == 0;
if (insert) {
key = execInsert(evar, newval.locator, key, val);
// Transfer the keys obtained from the db.
key.write(varType, newval.locator);
}
} catch (SQLException se) {
throw new ExternalVariableModuleException("Error updating row.", se);
}
return newval;
}
public Value readValue(QName varType, Locator locator) throws ExternalVariableModuleException {
EVarId evarId = new EVarId(locator.pid, locator.varId);
DbExternalVariable evar = _vars.get(evarId);
if (evar == null)
throw new ExternalVariableModuleException("No such variable: "+evarId);
Element val;
try {
RowVal rowval = execSelect(evar, locator);
val = evar.renderXmlRow(locator, varType, rowval);
} catch (SQLException se) {
throw new ExternalVariableModuleException("SQL Error.", se);
}
return new Value(locator, val, null);
}
/**
* Manually register a data source. Handy if you don't want to use JNDI to look these up.
*
* @param dsName
* @param ds
*/
public void registerDataSource(String dsName, DataSource ds) {
_dataSources.put(dsName, ds);
}
int execUpdate(DbExternalVariable dbev, RowKey key, RowVal values) throws SQLException {
Connection conn = dbev.dataSource.getConnection();
PreparedStatement stmt = null;
try {
if (__log.isDebugEnabled()) {
__log.debug("execUpdate: key=" + key + " values=" + values);
__log.debug("Prepare statement: " + dbev.update);
}
stmt = conn.prepareStatement(dbev.update);
int idx = 1;
for (Column c : dbev._updcolumns) {
Object val = values.get(c.name);
if (__log.isDebugEnabled()) __log.debug("Set value parameter "+idx+": "+val);
if (val == null)
stmt.setNull(idx, c.dataType);
else
stmt.setObject(idx, downcastValue(val, c.dataType));
idx++;
}
for (Column ck : dbev._keycolumns) {
Object val = key.get(ck.name);
if (__log.isDebugEnabled()) __log.debug("Set key parameter "+idx+": "+val);
if (val == null)
stmt.setNull(idx, ck.dataType);
else
stmt.setObject(idx, downcastValue(val, ck.dataType));
idx++;
}
return stmt.executeUpdate();
} finally {
if (stmt != null) stmt.close();
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
private Object downcastValue(Object value, int dataType) {
if (value == null) {
return null;
}
// Try down casting the value as per its column type.
try {
// Some JDBC 4.0 types have been ignored to avoid compilation errors
switch (dataType) {
case Types.ARRAY:
break;
case Types.BIGINT:
if (!(value instanceof BigInteger)) {
value = new BigDecimal(value.toString()).longValue();
}
break;
case Types.BINARY:
break;
case Types.BIT:
if (!(value instanceof Boolean)) {
value = new Boolean(value.toString());
}
break;
case Types.BLOB:
break;
case Types.BOOLEAN:
if (!(value instanceof Boolean)) {
value = new Boolean(value.toString());
}
break;
case Types.CHAR:
break;
case Types.CLOB:
break;
case Types.DATALINK:
break;
case Types.DATE:
break;
case Types.DECIMAL:
//ODE-872: Oracle 9g and 10g has problems with BigDecimal on Java1.5
value = new BigDecimal(new BigDecimal(value.toString()).toPlainString());
break;
case Types.DISTINCT:
break;
case Types.DOUBLE:
if (!(value instanceof Double)) {
value = Double.valueOf(value.toString()).doubleValue();
}
break;
case Types.FLOAT:
if (!(value instanceof Float)) {
value = Float.valueOf(value.toString()).floatValue();
}
break;
case Types.INTEGER:
if (!(value instanceof Integer)) {
value = Double.valueOf(value.toString()).intValue();
}
break;
case Types.JAVA_OBJECT:
break;
// case Types.LONGNVARCHAR:
// break;
case Types.LONGVARBINARY:
break;
case Types.LONGVARCHAR:
break;
// case Types.NCHAR:
// break;
// case Types.NCLOB:
// break;
case Types.NUMERIC:
//ODE-872: Oracle 9g and 10g has problems with BigDecimal on Java1.5
value = new BigDecimal(new BigDecimal(value.toString()).toPlainString());
break;
// case Types.NVARCHAR:
// break;
case Types.OTHER:
break;
case Types.REAL:
if (!(value instanceof Double)) {
value = Float.valueOf(value.toString()).floatValue();
}
break;
case Types.REF:
break;
// case Types.ROWID:
// break;
case Types.SMALLINT:
if (!(value instanceof Short)) {
value = new Short(value.toString()).shortValue();
}
break;
// case Types.SQLXML:
// break;
case Types.STRUCT:
break;
case Types.TIME:
break;
case Types.TIMESTAMP:
break;
case Types.TINYINT:
if (!(value instanceof Short)) {
value = new Short(value.toString()).shortValue();
}
break;
case Types.VARBINARY:
break;
case Types.VARCHAR:
break;
default:
break;
}
} catch (Exception e) {
// couldn't cast... let's just use original value object
}
return value;
}
RowVal execSelect(DbExternalVariable dbev, Locator locator) throws SQLException, ExternalVariableModuleException {
RowKey rowkey = dbev.keyFromLocator(locator);
if (__log.isDebugEnabled()) __log.debug("execSelect: " + rowkey);
if (rowkey.missingDatabaseGeneratedValues()) {
return null;
}
if (rowkey.missingValues()) {
throw new IncompleteKeyException(rowkey.getMissing());
}
RowVal ret = dbev.new RowVal();
Connection conn = dbev.dataSource.getConnection();
PreparedStatement stmt = null;
try {
if (__log.isDebugEnabled()) __log.debug("Prepare statement: " + dbev.select);
stmt = conn.prepareStatement(dbev.select);
int idx = 1;
for (Object k : rowkey) {
if (__log.isDebugEnabled()) __log.debug("Set key parameter "+idx+": "+k);
stmt.setObject(idx++, k);
}
ResultSet rs = stmt.executeQuery();
try {
if (rs.next()) {
for (Column c : dbev._columns) {
Object val;
int i = c.idx+1;
if (c.isDate()) val = rs.getDate(i);
else if (c.isTimeStamp()) val = rs.getTimestamp(i);
else if (c.isTime()) val = rs.getTime(i);
else if (c.isInteger()) val = new Long(rs.getLong(i));
else if (c.isReal()) val = new Double(rs.getDouble(i));
else if (c.isBoolean()) val = new Boolean(rs.getBoolean(i));
else val = rs.getObject(i);
if (__log.isDebugEnabled()) __log.debug("Result column index "+c.idx+": "+val);
ret.set(c.idx,val);
}
} else
return null;
} finally {
rs.close();
}
} finally {
if (stmt != null) stmt.close();
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
return ret;
}
RowKey execInsert(DbExternalVariable dbev, Locator locator, RowKey keys, RowVal values) throws SQLException {
Connection conn = dbev.dataSource.getConnection();
PreparedStatement stmt = null;
try {
if (__log.isDebugEnabled()) {
__log.debug("execInsert: keys=" + keys + " values=" + values);
__log.debug("Prepare statement: " + dbev.insert);
__log.debug("missingDatabaseGeneratedValues: " + keys.missingDatabaseGeneratedValues());
__log.debug("_autoColNames: " + ObjectPrinter.stringifyNvList(dbev._autoColNames));
}
stmt = keys.missingDatabaseGeneratedValues()
? conn.prepareStatement(dbev.insert, dbev._autoColNames)
: conn.prepareStatement(dbev.insert);
int idx = 1;
for (Column c : dbev._inscolumns) {
Object val = c.getValue(c.name, keys, values, locator.iid);
values.put(c.name, val);
if (__log.isDebugEnabled()) __log.debug("Set parameter "+idx+": "+val);
if (val == null)
stmt.setNull(idx, c.dataType);
else
stmt.setObject(idx, val);
idx++;
}
stmt.execute();
for (Column ck : keys._columns) {
Object val = values.get(ck.name);
if (__log.isDebugEnabled()) __log.debug("Key "+ck.name+": "+val);
keys.put(ck.name,val);
}
if (keys.missingDatabaseGeneratedValues() ) {
// With JDBC 3, we can get the values of the key columns (if the db supports it)
ResultSet keyRS = stmt.getGeneratedKeys();
try {
if (keyRS == null)
throw new SQLException("Database did not return generated keys");
keyRS.next();
for (Column ck : keys._columns) {
Object value = keyRS.getObject(ck.idx+1);
if (__log.isDebugEnabled()) __log.debug("Generated key "+ck.name+": "+value);
keys.put(ck.name, value);
}
} finally {
keyRS.close();
}
}
return keys;
} finally {
if (stmt != null) stmt.close();
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
}