/* * 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.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import javax.sql.DataSource; import javax.xml.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ode.utils.DOMUtils; import org.apache.ode.utils.GUID; import org.apache.ode.utils.ISO8601DateParser; import org.apache.ode.bpel.evar.ExternalVariableModuleException; import org.apache.ode.bpel.evar.ExternalVariableModule.Locator; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Configuration for an external variable. * * @author Maciej Szefler <mszefler at gmail dot com> */ class DbExternalVariable { private static final Log __log = LogFactory.getLog(DbExternalVariable.class); private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"; EVarId evarId; DataSource dataSource; final ArrayList<Column> _columns = new ArrayList<Column>(); private final HashMap<String, Column> _colmap = new HashMap<String, Column>(); final ArrayList<Column> _keycolumns = new ArrayList<Column>(); final ArrayList<Column> _inscolumns = new ArrayList<Column>(); final ArrayList<Column> _updcolumns = new ArrayList<Column>(); InitType _initType = InitType.update_insert; public String[] _autoColNames; String select; String insert; String update; String table; String schema; // table schema /** Does the database support retrieval of generated keys? */ boolean generatedKeys; DbExternalVariable(EVarId evar, DataSource ds) { this.evarId = evar; this.dataSource = ds; } Column getColumn(String key) { return _colmap.get(key); } void addColumn(Column c) { c.idx = _columns.size(); _colmap.put(c.name, c); _columns.add(c); if (c.key) { _keycolumns.add(c); _autoColNames = new String[_keycolumns.size()]; for (int i = 0; i < _autoColNames.length; ++i) _autoColNames[i] = _keycolumns.get(i).colname; } createSelect(); createInsert(); createUpdate(); } public int numColumns() { return _columns.size(); } /** * Create a key from a locator. */ RowKey keyFromLocator(Locator locator) throws ExternalVariableModuleException { RowKey rc = new RowKey(); parseXmlRow(rc, locator.reference); // Put in the static goodies such as pid/iid for (Column c : rc._columns) { switch (c.genType) { case iid: case pid: rc.put(c.name, c.getValue(c.name, null, null, locator.iid)); break; } } return rc; } private void createSelect() { StringBuilder sb = new StringBuilder("select "); boolean first = true; for (Column c : _columns) { if (!first) { sb.append(','); } first = false; sb.append(c.colname); } sb.append(" from " + table); if (_keycolumns.size() > 0) { sb.append(" where "); first = true; for (Column kc : _keycolumns) { if (!first) { sb.append(" and "); } first = false; sb.append(kc.colname); sb.append(" = ?"); } select = sb.toString(); } else { select = null; } } private void createUpdate() { _updcolumns.clear(); StringBuilder sb = new StringBuilder("update "); sb.append(table); sb.append(" set "); boolean first = true; for (Column c : _columns) { // Don't ever update keys or sequences or create time stamps if (c.genType == GenType.sequence || c.key || c.genType == GenType.ctimestamp) continue; if (!first) sb.append(", "); first = false; sb.append(c.colname); sb.append(" = "); if (c.genType == GenType.expression) sb.append(c.expression); else { sb.append(" ?"); _updcolumns.add(c); } } if (_keycolumns.size() > 0) { sb.append(" where "); first = true; for (Column kc : _keycolumns) { if (!first) { sb.append(" and "); } first = false; sb.append(kc.colname); sb.append(" = ?"); } } // If we have no key columns, we cannot do an update if (_keycolumns.size() == 0) update = null; else update = sb.toString(); } private void createInsert() { _inscolumns.clear(); StringBuilder sb = new StringBuilder("insert into "); sb.append(table); sb.append(" ( "); boolean first = true; for (Column c : _columns) { if (c.genType == GenType.sequence) continue; if (!first) sb.append(','); first = false; sb.append(c.colname); } sb.append(" ) "); sb.append(" values ( "); first = true; for (Column c : _columns) { if (c.genType == GenType.sequence) continue; if (!first) sb.append(','); first = false; if (c.genType == GenType.expression) sb.append(c.expression); else { sb.append(" ? "); _inscolumns.add(c); } } sb.append(" ) "); insert = sb.toString(); } <T extends RowSubset> Element renderXmlRow(Locator locator, QName varType, T value) throws ExternalVariableModuleException { Document doc = DOMUtils.newDocument(); Element el = doc.createElementNS(varType.getNamespaceURI(), varType.getLocalPart()); doc.appendChild(el); if (value != null) { for (Column c : value._columns) { Object data = value.get(c.idx); addElement(el, varType, c, data); } } else { // initialize variable with default/generated values RowKey keys = keyFromLocator(locator); for (Column c : _columns) { Object data = c.getValue(c.name, keys, new RowVal(), locator.iid); addElement(el, varType, c, data); } } return el; } private void addElement(Element parent, QName varType, Column c, Object data) { Document doc = parent.getOwnerDocument(); Element cel = doc.createElementNS(varType.getNamespaceURI(), c.name); String strdat = c.toText(data); if (strdat != null) { cel.appendChild(doc.createTextNode(strdat)); } else if (c.nullok || c.isGenerated()) { cel.setAttributeNS(XSI_NS, "xsi:nil", "true"); } parent.appendChild(cel); } <T extends RowSubset> T parseXmlRow(T ret, Node rowel) throws ExternalVariableModuleException { if (rowel == null) return ret; NodeList nl = rowel.getChildNodes(); if (__log.isDebugEnabled()) __log.debug("parseXmlRow: element="+rowel.getLocalName()); for (int i = 0; i < nl.getLength(); ++i) { Node n = nl.item(i); if (n.getNodeType() != Node.ELEMENT_NODE) continue; String key = n.getLocalName(); String val = n.getTextContent(); if (__log.isDebugEnabled()) __log.debug("Extvar key: "+key+" value: "+val); Column column = ret.getColumn(key); if (column == null) { if (__log.isDebugEnabled()) __log.debug("No matching column for key '"+key+"'"); continue; } String nil = ((Element) n).getAttributeNS(XSI_NS, "nil"); if (nil != null && "true".equalsIgnoreCase(nil) && (val == null || val.trim().length() == 0)) { if (__log.isDebugEnabled()) __log.debug("Extvar key: "+key+" is null (xsi:nil)"); ret.put(key, null); } else { ret.put(key, column.fromText(val)); } } return ret; } class Column { int idx; /** name of the column */ final String name; /** database name of the column (in case we need to override */ final String colname; /** Is this a key column? */ final boolean key; /** Type of value generator to use for creating values for this column. */ final GenType genType; /** The (SQL) expression used to populate the column. */ final String expression; /** The SQL data type of this column, one of java.sql.Types */ int dataType; /** Indicates NULL values are OK */ boolean nullok; Column(String name, String colname, boolean key, GenType genType, String expression) { this.name = name; this.colname = colname == null ? name : colname; this.key = key; this.genType = genType; this.expression = expression; } public Object getValue(String name, RowKey keys, RowVal values, Long iid) { switch (genType) { case ctimestamp: case utimestamp: return isTimeStamp() ? new Timestamp(new Date().getTime()) : new Date(); case uuid: return new GUID().toString(); case pid: return evarId.pid.toString(); case iid: return iid; case none: default: if (key && keys.get(name) != null) return keys.get(name); else return values.get(name); } } boolean supportsEmptyValue() { return (dataType == Types.VARCHAR || dataType == Types.LONGVARCHAR || dataType == Types.CLOB); } /** * Return <code>true</code> if column is a date-like type. */ boolean isDate() { return dataType == Types.DATE; } boolean isTimeStamp() { return dataType == Types.TIMESTAMP; } boolean isTime() { return dataType == Types.TIME; } /** * Is this column best represented as an integer? */ boolean isInteger() { switch (dataType) { case Types.BIGINT: case Types.INTEGER: case Types.SMALLINT: case Types.TINYINT: return true; default: return false; } } /** * Is this column best represented as a real number? */ boolean isReal() { switch (dataType) { case Types.DECIMAL: case Types.REAL: case Types.NUMERIC: return true; default: return false; } } boolean isBoolean() { switch (dataType) { case Types.BIT: return true; default: return false; } } String toText(Object val) { if (val == null) return null; Date date = null; if (val instanceof java.util.Date) { // also applies to java.sql.Time, java.sql.Timestamp date = (Date) val; return ISO8601DateParser.format((Date) val); } return val.toString(); } Object fromText(String val) throws ExternalVariableModuleException { try { if (val == null) return null; if (!supportsEmptyValue() && val.trim().length() == 0) { return null; } // TODO: use xsd:date and xsd:time conversions if (isDate()) return new java.sql.Date(ISO8601DateParser.parse(val).getTime()); else if (isTime()) return new java.sql.Time(ISO8601DateParser.parse(val).getTime()); else if (isTimeStamp()) return new java.sql.Timestamp(ISO8601DateParser.parse(val).getTime()); else if (isInteger()) { String v = val.trim().toLowerCase(); if (v.equals("true")) return 1; if (v.equals("false")) return 0; return Long.valueOf(val); } else if (isReal()) return Double.valueOf(val); else if (isBoolean()) { String v = val.trim(); if (v.equals("1")) return true; if (v.equals("0")) return false; return Boolean.valueOf(val); } return val; } catch (Exception ex) { throw new ExternalVariableModuleException( "Unable to convert value \"" + val + "\" for column \"" + name + "\" !", ex); } } public boolean isGenerated() { return (genType != null && !genType.equals(GenType.none)); } public boolean isDatabaseGenerated() { return isGenerated() && (genType.equals(GenType.sequence) || genType.equals(GenType.expression)); } public String toString() { return "Column {idx="+idx +",name="+name +",colname="+colname +",key="+key +",genType="+genType +")"; } } /** * Key used to identify a row. */ class RowKey extends RowSubset { private static final long serialVersionUID = 1L; /** * Create empty row key. */ RowKey() { super(_keycolumns); } /** * Write the key to a locator. */ void write(QName varType, Locator locator) throws ExternalVariableModuleException { locator.reference = renderXmlRow(locator, varType, this); } public Set<String> getMissing() { HashSet<String> missing = new HashSet<String>(); for (Column c : _keycolumns) { if (get(c.idx) == null) missing.add(c.name); } return missing; } } /** * Row values. */ class RowVal extends RowSubset { private static final long serialVersionUID = 1L; RowVal() { super(DbExternalVariable.this._columns); } } }