| /* |
| * 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 new java.math.BigDecimal(val).longValue(); |
| } 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); |
| } |
| } |
| |
| } |