/**
* Copyright 2008-2016 Qualogy Solutions B.V.
*
* Licensed 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 com.qualogy.qafe.business.resource.rdb.query.call;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import oracle.jdbc.oracore.OracleTypeADT;
import oracle.jdbc.oracore.OracleTypeCOLLECTION;
import oracle.jdbc.oracore.OracleTypeUPT;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;
import oracle.sql.TypeDescriptor;
import com.qualogy.qafe.bind.commons.type.AdapterAttribute;
import com.qualogy.qafe.bind.commons.type.AdapterMapping;
import com.qualogy.qafe.bind.commons.type.AttributeMapping;
import com.qualogy.qafe.bind.commons.type.In;
import com.qualogy.qafe.bind.commons.type.Out;
import com.qualogy.qafe.bind.commons.type.Reference;
import com.qualogy.qafe.bind.integration.service.Method;
import com.qualogy.qafe.bind.resource.query.Call;
import com.qualogy.qafe.business.integration.filter.Filters;
import com.qualogy.qafe.business.integration.filter.page.Page;
import com.qualogy.qafe.business.integration.filter.page.ResultSetDataExtractor;
import com.qualogy.qafe.business.integration.rdb.MetaDataRowMapper;
import com.qualogy.qafe.commons.db.procedure.CallArgument;
import com.qualogy.qafe.commons.db.procedure.CallArguments;
import com.qualogy.qafe.core.datastore.DataMap;
import com.qualogy.qafe.core.datastore.DataStore;
/**
* Oracle specific instance of the DBCall class.
*/
public class BaseCall extends DBCall {
private static final Logger logger = Logger.getLogger(BaseCall.class.getName());
private static final String TYPE_TABLE = "TABLE";
private static final String TYPE_REF_CURSOR = "REF CURSOR";
private static final String TYPE_REF = "REF";
private static final String RETURN_NAME = "result";
private boolean supportsNamedParameters = true;
public BaseCall(boolean supportsNamedParameters) {
this.supportsNamedParameters = supportsNamedParameters;
}
/**
* could have called callStmt.getParameterMetaData(); per call but cheaper to do it ones
*/
public CallableStatement prepareCall(Connection connection, Call call, Method method, Map inputParameters)
throws SQLException {
Connection conn = NativeConnectionHandler.getNativeConnection(connection);
String sql = null;
if (!call.isPrepared()) {
if (call.isFunction()) {
sql = createFunction(call);
} else {
sql = createProcedure(call);
}
} else {
sql = call.getSql();
}
CallableStatement callStmt = conn.prepareCall(sql);
CallArguments arguments = call.getArguments();
if (arguments != null) {
prepareOutputs(conn, callStmt, method, arguments.getAllResultableArgs());
prepareInputs(conn, callStmt, method, arguments.getInArgs(), inputParameters);
}
return callStmt;
}
private void prepareOutputs(Connection conn, CallableStatement callStmt, Method method, List outputArgs)
throws SQLException {
if (outputArgs != null) {
for (Object outputArg : outputArgs) {
if (outputArg instanceof CallArgument) {
CallArgument argument = (CallArgument) outputArg;
registerOutput(conn, callStmt, argument, method.getOutput());
}
}
}
}
private void registerOutput(Connection conn, CallableStatement callStmt, CallArgument argument,
List<Out> outputs) throws SQLException {
if (argument == null) {
return;
}
AdapterMapping outputAdapter = getOutputAdapter(argument, outputs);
int argumentIndex = argument.getSequence();
int argumentType = argument.getSqlType();
String argumentTypeName = getArgumentTypeName(outputAdapter, argument.getTypeName());
if (isStructType(argument)) {
argumentType = Types.STRUCT;
callStmt.registerOutParameter(argumentIndex, argumentType, argumentTypeName);
} else if (isArrayOfStructType(argument)) {
argumentType = Types.ARRAY;
callStmt.registerOutParameter(argumentIndex, argumentType, argumentTypeName);
} else {
callStmt.registerOutParameter(argumentIndex, argumentType);
}
}
private void prepareInputs(Connection conn, CallableStatement callStmt, Method method, List inputArgs,
Map inputValues) throws SQLException {
if (inputArgs == null) {
return;
}
Connection nativeConn = conn.getMetaData().getConnection();
for (Object inputArg : inputArgs) {
if (inputArg instanceof CallArgument) {
CallArgument argument = (CallArgument) inputArg;
int argumentIndex = argument.getSequence();
String argumentTypeName = argument.getTypeName();
int argumentType = argument.getSqlType();
if (isStructType(argument)) {
argumentType = Types.STRUCT;
} else if (isArrayOfStructType(argument)) {
argumentType = Types.ARRAY;
}
AdapterMapping inputAdapter = getInputAdapter(argument, method.getInput());
Object argumentValue = null;
String argumentName = argument.getArgumentName();
if (inputValues.containsKey(argumentName)) {
Object inputValue = inputValues.get(argumentName);
argumentValue =
getArgumentValue(nativeConn, argumentName, argumentType, argumentTypeName,
inputValue, inputAdapter);
} else {
String inputIndex = String.valueOf(argumentIndex - 1);
if (inputValues.containsKey(inputIndex)) {
Object inputValue = inputValues.get(inputIndex);
argumentValue =
getArgumentValue(nativeConn, inputIndex, argumentType, argumentTypeName,
inputValue, inputAdapter);
} else {
// TODO
}
}
callStmt.setObject(argumentIndex, argumentValue, argumentType);
}
}
}
private Object getArgumentValue(Connection nativeConn, String argumentName, int argumentType,
String argumentTypeName, Object inputValue, AdapterMapping inputAdapter) throws SQLException {
Object argumentValue = inputValue;
argumentTypeName = getArgumentTypeName(inputAdapter, argumentTypeName);
Map<String, Object> objectAttributes =
getObjectAttributes(nativeConn, argumentType, argumentTypeName);
if (argumentType == Types.STRUCT) {
Object[] struct = createStruct(objectAttributes, (Map) argumentValue, inputAdapter);
argumentValue = nativeConn.createStruct(argumentTypeName, struct);
} else if (argumentType == Types.ARRAY) {
List<Object[]> listOfStruct = new ArrayList<Object[]>();
if (argumentValue instanceof Iterable) {
Iterator itr = ((Iterable) argumentValue).iterator();
while (itr.hasNext()) {
Object obj = itr.next();
if (obj instanceof Map) {
Object[] struct = createStruct(objectAttributes, (Map) obj, inputAdapter);
if (struct != null) {
listOfStruct.add(struct);
}
}
}
}
TypeDescriptor typeDescriptor = getTypeDescriptor(nativeConn, argumentType, argumentTypeName);
if (typeDescriptor instanceof ArrayDescriptor) {
argumentValue =
new ARRAY((ArrayDescriptor) typeDescriptor, nativeConn, listOfStruct.toArray());
}
} else {
if (argumentValue instanceof Date) {
argumentValue = new java.sql.Date(((Date) argumentValue).getTime());
}
}
return argumentValue;
}
private AdapterMapping getInputAdapter(CallArgument argument, List<In> inputs) {
for (In input : inputs) {
String inputName = input.getName();
if (isArgument(argument, inputName)) {
return input.getAdapter();
}
}
return null;
}
private AdapterMapping getOutputAdapter(CallArgument argument, List<Out> outputs) {
for (Out output : outputs) {
String outputName = output.getName();
String referenceName = null;
Reference reference = output.getRef();
if (reference != null) {
referenceName = reference.stringValueOf();
}
if (isArgument(argument, referenceName) || isArgument(argument, outputName)) {
return output.getAdapter();
}
}
return null;
}
private boolean isArgument(CallArgument argument, String paramName) {
String argumentName = argument.getArgumentName();
if ((argumentName != null) && argumentName.equals(paramName)) {
return true;
} else {
int argumentIndex = argument.getSequence();
if (String.valueOf(argumentIndex).equals(paramName)) {
return true;
}
}
return false;
}
private String getArgumentTypeName(AdapterMapping adapter, String argumentTypeName) {
if (adapter == null) {
return argumentTypeName;
}
return adapter.getAdaptName();
}
private Object[] createStruct(Map<String, Object> attributes, Map data, AdapterMapping adapter) {
Object[] struct = new Object[attributes.size()];
if (data != null) {
int attributeIndex = 0;
Iterator<String> itrAttribute = attributes.keySet().iterator();
while (itrAttribute.hasNext()) {
String attribute = itrAttribute.next();
if ((adapter != null) && (adapter.adaptAll() == false)) {
attribute = getAdapterAttribute(attribute, adapter);
}
Object attributeValue = data.get(attribute);
if (attributeValue instanceof Date) {
attributeValue = new java.sql.Date(((Date) attributeValue).getTime());
} else if ("".equals(attributeValue)) {
// TODO
attributeValue = null;
}
struct[attributeIndex] = attributeValue;
attributeIndex++;
}
}
return struct;
}
private String getAdapterAttribute(String attribute, AdapterMapping adapter) {
String adapterAttribute = null;
List<AdapterAttribute> adapterAttributes = adapter.getAdapterAttributes();
if (adapterAttributes == null) {
return adapterAttribute;
}
for (AdapterAttribute adapterAttr : adapterAttributes) {
if (adapterAttr instanceof AttributeMapping) {
AttributeMapping attributeMapping = (AttributeMapping) adapterAttr;
if (attributeMapping.getName().equals(attribute)) {
Reference reference = attributeMapping.getRef();
if (reference != null) {
adapterAttribute = reference.stringValueOf();
}
break;
}
}
}
if (adapterAttribute == null) {
AdapterMapping parentAdapter = adapter.getParent();
if (parentAdapter != null) {
adapterAttribute = getAdapterAttribute(attribute, parentAdapter);
}
}
return adapterAttribute;
}
private Map<String, Object> getObjectAttributes(Connection nativeConn, int dataType, String dataTypeName)
throws SQLException {
Map<String, Object> objectAttributes = new LinkedHashMap<String, Object>();
TypeDescriptor typeDescriptor = getTypeDescriptor(nativeConn, dataType, dataTypeName);
if (typeDescriptor != null) {
OracleTypeADT oracleTypeADT = (OracleTypeADT) typeDescriptor.getPickler();
if (oracleTypeADT instanceof OracleTypeCOLLECTION) {
OracleTypeCOLLECTION oracleTypeCOLLECTION = (OracleTypeCOLLECTION) oracleTypeADT;
OracleTypeUPT oracleTypeUPT = (OracleTypeUPT) oracleTypeCOLLECTION.getElementType();
oracleTypeADT = (OracleTypeADT) oracleTypeUPT.getRealType();
}
int numAttributes = oracleTypeADT.getNumAttrs();
for (int i = 0; i < numAttributes; i++) {
int attributeIndex = i + 1;
String attributeName = oracleTypeADT.getAttributeName(attributeIndex);
Object attributeType = oracleTypeADT.getAttributeType(attributeIndex);
objectAttributes.put(attributeName, attributeType);
}
}
return objectAttributes;
}
private TypeDescriptor getTypeDescriptor(Connection nativeConn, int dataType, String dataTypeName)
throws SQLException {
if (dataType == Types.STRUCT) {
StructDescriptor structDescriptor = StructDescriptor.createDescriptor(dataTypeName, nativeConn);
return structDescriptor;
}
if (dataType == Types.ARRAY) {
ArrayDescriptor arrayDescriptor = ArrayDescriptor.createDescriptor(dataTypeName, nativeConn);
return arrayDescriptor;
}
return null;
}
private boolean isStructType(CallArgument argument) {
// TYPE_NAME for the Object type in database comes like KPMG.LNE_TYP (i.e. SCHEMA.OBJECTNAME),
// from this we cannot identify if it is object type or not, but in other types like Refcursor or
// Table or REF we are getting the type name.
// So If the argument type name is not in the list we know that it is a Object type in database which
// is equivalent to Java type STRUCT
if (argument == null) {
return false;
}
if (argument.getSqlType() != CallArgument.TYPE_OTHER) {
return false;
}
String argumentTypeName = argument.getTypeName();
if (TYPE_REF.equals(argumentTypeName)) {
return false;
}
if (TYPE_REF_CURSOR.equals(argumentTypeName)) {
return false;
}
if (TYPE_TABLE.equals(argumentTypeName)) {
return false;
}
return true;
}
private boolean isArrayOfStructType(CallArgument argument) {
if (argument == null) {
return false;
}
if (argument.getSqlType() != CallArgument.TYPE_OTHER) {
return false;
}
String argumentTypeName = argument.getTypeName();
if (TYPE_TABLE.equals(argumentTypeName)) {
return true;
}
return false;
}
protected String createProcedure(Call call) {
String sql = "{" + CALLABLESTATEMENT_KEYWORD + " " + call.getCallName();
int numArgs = call.getArguments().size();
if (numArgs > 0) {
sql += "(";
for (int i = 0; i < numArgs; i++) {
sql += (i != (numArgs - 1)) ? "?," : "?";
}
sql += ")";
}
sql += "}";
return sql;
}
protected String createFunction(Call call) {
String sql = "{";
int numOutputArgs = call.getArguments().getResultArgs().size();
if (numOutputArgs > 0) {
for (int i = 0; i < numOutputArgs; i++) {
sql += (i != (numOutputArgs - 1)) ? "?," : "?";
}
sql += " = ";
}
sql += CALLABLESTATEMENT_KEYWORD + " " + call.getCallName();
int numParamArgs = call.getArguments().getParameterArgs().size();
if (numParamArgs > 0) {
sql += "(";
for (int i = 0; i < numParamArgs; i++) {
sql += (i != (numParamArgs - 1)) ? "?," : "?";
}
sql += ")";
}
sql += "}";
return sql;
}
// CHECKSTYLE.OFF: CyclomaticComplexity
public Map readResults(CallableStatement callStmt, Call call, Method method, Set outputs, Filters filters)
throws SQLException {
Map callResult = new HashMap();
List outputArgs = call.getArguments().getAllResultableArgs();
if (outputArgs != null) {
for (Object outputArg : outputArgs) {
if (outputArg instanceof CallArgument) {
CallArgument argument = (CallArgument) outputArg;
int argumentIndex = argument.getSequence();
String argumentName = argument.getArgumentName();
String outputName = null;
if (DataStore.KEY_WORD_QAFE_BUILT_IN_LIST.equalsIgnoreCase(argumentName)) {
outputName = DataStore.KEY_WORD_QAFE_BUILT_IN_LIST;
} else {
outputName = String.valueOf(argumentIndex - 1);
if (!outputs.contains(outputName)) {
if (argumentName != null) {
if (!outputs.contains(argumentName) || !supportsNamedParameters) {
logger
.info("parameter ["
+ outputName
+ "]["
+ argumentName
+ "] is ignored since its not in the outputMapping if this service");
continue;
}
outputName = argumentName;
} else {
// Default name for a return argument of a function call,
// e.g: ? = call my_function(), ? <=> result
outputName = RETURN_NAME;
}
}
}
AdapterMapping outputAdapter = getOutputAdapter(argument, method.getOutput());
Object outputValue = null;
Object stmtResult = callStmt.getObject(argumentIndex);
if (stmtResult instanceof STRUCT) {
STRUCT objectStruct = (STRUCT) stmtResult;
outputValue = createDataMap(objectStruct, outputAdapter);
} else if (stmtResult instanceof ARRAY) {
ARRAY objectArray = (ARRAY) stmtResult;
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Object array = objectArray.getArray();
if (array instanceof Object[]) {
Object[] arrayOfObject = (Object[]) array;
for (Object object : arrayOfObject) {
if (object instanceof STRUCT) {
STRUCT objectStruct = (STRUCT) object;
Map<String, Object> map = createDataMap(objectStruct, outputAdapter);
if (map != null) {
list.add(map);
}
}
}
}
outputValue = list;
} else if (stmtResult instanceof ResultSet) {
ResultSet resultSet = (ResultSet) stmtResult;
List<Object> list = new ArrayList<Object>();
MetaDataRowMapper rowMapper = new MetaDataRowMapper();
if ((filters != null) && (filters.getPage() != null)) {
Page page = filters.getPage();
page = (Page) new ResultSetDataExtractor(page, rowMapper).extractData(resultSet);
list = page.getPageItems();
} else {
while (resultSet.next()) {
list.add(rowMapper.mapRow(resultSet, resultSet.getRow()));
}
}
outputValue = list;
} else {
outputValue = stmtResult;
}
callResult.put(outputName, outputValue);
}
}
}
return callResult;
}
// CHECKSTYLE.ON: CyclomaticComplexity
private Map<String, Object> createDataMap(STRUCT objectStruct, AdapterMapping adapter)
throws SQLException {
Map<String, Object> map = null;
if (objectStruct != null) {
map = new DataMap<String, Object>();
OracleTypeADT oracleTypeADT = (OracleTypeADT) objectStruct.getDescriptor().getPickler();
int numAttributes = oracleTypeADT.getNumAttrs();
Object[] attributeValues = objectStruct.getAttributes();
for (int i = 0; i < numAttributes; i++) {
int attributeIndex = i + 1;
String attributeName = oracleTypeADT.getAttributeName(attributeIndex);
if ((adapter != null) && (adapter.adaptAll() == false)) {
attributeName = getAdapterAttribute(attributeName, adapter);
}
if (attributeName != null) {
Object attributeValue = attributeValues[i];
map.put(attributeName, attributeValue);
}
}
}
return map;
}
}