/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.soap.provider;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.soap.SOAPFaultException;
import org.teiid.soap.SoapPlugin;
// Only include following BindingType if using SOAP1.2
// @BindingType(value =
// "http://java.sun.com/xml/ns/jaxws/2003/05/soap/bindings/HTTP/")
public class TeiidWSProvider {
/*
* These are the standard SOAP 1.1 fault codes that we use in the Data
* Service web service implementation to report fault conditions to the
* user.
*/
public static final String SOAP_11_STANDARD_CLIENT_FAULT_CODE = "Client"; //$NON-NLS-1$
public static final String SOAP_11_STANDARD_SERVER_FAULT_CODE = "Server"; //$NON-NLS-1$
protected WebServiceContext webServiceContext;
private static Logger logger = Logger.getLogger("org.teiid.soap"); //$NON-NLS-1$
@javax.annotation.Resource
protected void setWebServiceContext(WebServiceContext wsc) {
webServiceContext = wsc;
}
public DataSource getDataSource(Properties properties) throws NamingException {
InitialContext ctx;
DataSource ds = null;
ctx = new InitialContext();
ds = (DataSource) ctx.lookup(properties.getProperty("jndiName")); //$NON-NLS-1$
return ds;
}
public Source execute(String procedureName, String inputMessage, String wsdlOperation, Properties properties)
throws SOAPFaultException, SOAPException {
Connection conn = null;
PreparedStatement statement = null;
ResultSet set = null;
// Get a connection
Source returnFragment = null;
try {
DataSource ds = getDataSource(properties);
conn = ds.getConnection();
String responseString;
boolean noParm = false;
if (inputMessage.equals("")) { //$NON-NLS-1$
noParm = true;
}
final String executeStatement = "call " + procedureName + (noParm ? "()" : "(?)") + ";"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
statement = conn.prepareStatement(executeStatement);
if (!noParm) {
statement.setString(1, inputMessage);
}
final boolean hasResultSet = statement.execute();
if (hasResultSet) {
set = statement.getResultSet();
if (set.next()) {
/*
* an XML result set that is appropriate for a Data Service
* web service will ALWAYS return a single XML Document
* result. The first row in the first column. If there are
* additional rows, we throw an exception as this resultset
* is not appropriate for a Data Service.
*/
SQLXML sqlXml = (SQLXML)set.getObject(1);
responseString = ((SQLXML)sqlXml).getString();
InputStream is = null;
try {
is = new ByteArrayInputStream(responseString
.getBytes("UTF-8")); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
logger.log(Level.SEVERE, SoapPlugin.Util
.getString("TeiidWSProvider.1") //$NON-NLS-1$
+ procedureName);
createSOAPFaultMessage(e, e.getMessage(),
SOAP_11_STANDARD_SERVER_FAULT_CODE);
}
returnFragment = new StreamSource(is);
} else {
logger.log(Level.WARNING, SoapPlugin.Util
.getString("TeiidWSProvider.8") //$NON-NLS-1$
+ procedureName);
createSOAPFaultMessage(new Exception(SoapPlugin.Util
.getString("TeiidWSProvider.2")), //$NON-NLS-1$
SoapPlugin.Util.getString("TeiidWSProvider.3"), //$NON-NLS-1$
SOAP_11_STANDARD_SERVER_FAULT_CODE);
}
if (set.next()) {
createSOAPFaultMessage(new Exception(SoapPlugin.Util
.getString("TeiidWSProvider.4") //$NON-NLS-1$
+ wsdlOperation
+ SoapPlugin.Util.getString("TeiidWSProvider.5")), //$NON-NLS-1$
SoapPlugin.Util.getString("TeiidWSProvider.6"), //$NON-NLS-1$
SOAP_11_STANDARD_SERVER_FAULT_CODE);
}
set.close();
}
statement.close();
/*
* If we fall through to here and no XML Fragment has been set on
* the returnMessage instance because 'hasResults' was false, then
* the return message is an empty message with no body contents. We
* do this only because we do not know what to do with a returned
* update count. We cannot return it as the body of the message
* because we will likely violate the schema type that defines the
* return message. The only thing i can think to do is to return an
* empty message in this instance. We really should handle this
* situation more explicitly in the future. (ie Operations in Web
* Service Models in the modeler should be able to be considered
* 'update' type operations and return a simple int).
*/
} catch (SQLException e) {
String faultcode = SOAP_11_STANDARD_SERVER_FAULT_CODE;
String msg = SoapPlugin.Util.getString("TeiidWSProvider.1"); //$NON-NLS-1$
logger.logrb(Level.SEVERE, "TeiidWSProvider", "execute", SoapPlugin.PLUGIN_ID, msg, new Throwable(e)); //$NON-NLS-1$ //$NON-NLS-2$
if (e instanceof SQLException) {
final SQLException sqlException = (SQLException) e;
if (SQLStates.isUsageErrorState(sqlException.getSQLState())) {
faultcode = SOAP_11_STANDARD_CLIENT_FAULT_CODE;
}
}
createSOAPFaultMessage(e, e.getMessage(), faultcode);
} catch (Exception e) {
String faultcode = SOAP_11_STANDARD_SERVER_FAULT_CODE;
String msg = SoapPlugin.Util.getString("TeiidWSProvider.1"); //$NON-NLS-1$
logger.logrb(Level.SEVERE, "TeiidWSProvider", "execute", SoapPlugin.PLUGIN_ID, msg, new Throwable(e)); //$NON-NLS-1$ //$NON-NLS-2$
if (e instanceof SQLException) {
final SQLException sqlException = (SQLException) e;
if (SQLStates.isUsageErrorState(sqlException.getSQLState())) {
faultcode = SOAP_11_STANDARD_CLIENT_FAULT_CODE;
}
}
createSOAPFaultMessage(e, e.getMessage(), faultcode);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
/*
* In this case, we do not return a SOAP fault to the
* customer. We simply log this problem in the log. If we do
* a return from the finally block, we will override the
* return in the try/catch that will either return the
* 'true' error or return a valid result document. Either
* way we do not want to return a SOAP fault just because
* closing the connection failed.
*/
String msg = SoapPlugin.Util.getString(
"TeiidWSProvider.1"); //$NON-NLS-1$
logger.logrb(Level.SEVERE, "TeiidWSProvider", "execute", SoapPlugin.PLUGIN_ID, msg, new Throwable(e)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
return returnFragment;
}
protected void createSOAPFaultMessage(final Exception e,
final String faultSoapPluginString, final String faultCode)
throws SOAPFaultException, SOAPException {
SOAPFactory fac = SOAPFactory.newInstance();
SOAPFault sf = fac.createFault(faultSoapPluginString, new QName(
"http://schemas.xmlsoap.org/soap/envelope/", faultCode)); //$NON-NLS-1$
throw new SOAPFaultException(sf);
}
private Properties loadProperties(Properties properties) throws SOAPFaultException, SOAPException {
try {
// Get the inputStream
InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("teiidsoap.properties"); //$NON-NLS-1$
properties = new Properties();
// load the inputStream using the Properties
properties.load(inputStream);
} catch (IOException e) {
String msg = SoapPlugin.Util.getString(
"TeiidWSProvider.1"); //$NON-NLS-1$
logger.logrb(Level.SEVERE, "TeiidWSProvider", "loadProperties", SoapPlugin.PLUGIN_ID, msg, new Throwable(e)); //$NON-NLS-1$ //$NON-NLS-2$
throw new RuntimeException(e);
}
return properties;
}
public javax.xml.transform.Source invoke(Source request) {
javax.xml.transform.Source response = null;
String inputMessage = ""; //$NON-NLS-1$
String procedureName = ""; //$NON-NLS-1$
String wsdlOperation = ""; //$NON-NLS-1$
Properties properties = new Properties();
MessageContext mc = webServiceContext.getMessageContext();
Object wsdlOperationQName = mc.get(MessageContext.WSDL_OPERATION);
// Load the properties object
try {
properties = loadProperties(properties);
} catch (SOAPFaultException e) {
logger.log(Level.SEVERE, e.getMessage());
throw new RuntimeException(e);
} catch (SOAPException e) {
logger.log(Level.SEVERE, e.getMessage());
throw new RuntimeException(e);
}
// Get procedure name from properties object based on operation name
if (wsdlOperationQName != null) {
wsdlOperation = ((QName) wsdlOperationQName).getLocalPart();
procedureName = properties.getProperty(wsdlOperation);
}
if (request == null) {
// No parameters
} else {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
StreamResult sr = new StreamResult(bos);
Transformer trans = TransformerFactory.newInstance()
.newTransformer();
trans.transform(request, sr);
inputMessage = bos.toString();
bos.close();
} catch (Exception e) {
logger.log(Level.WARNING, SoapPlugin.Util
.getString("TeiidWSProvider.7") //$NON-NLS-1$
+ procedureName);
throw new RuntimeException(e);
}
}
try {
response = execute(procedureName, inputMessage, wsdlOperation, properties);
} catch (SOAPFaultException e) {
throw new RuntimeException(e);
} catch (SOAPException e) {
logger.log(Level.SEVERE, SoapPlugin.Util
.getString("TeiidWSProvider.9") //$NON-NLS-1$
+ procedureName);
throw new RuntimeException(e);
}
return response;
}
/**
* Utility class containing 1) SQL state constants used to represent JDBC error state code, and
* 2) utility methods to check whether a SQL state belongs to a particular class of exception states.
* @since 4.3
*/
public static class SQLStates {
/**
* Identifies the SQLState class Connection Exception (08).
*/
public static final SQLStateClass CLASS_CONNECTION_EXCEPTION = new SQLStateClass(
"08"); //$NON-NLS-1$
/**
* Connection Exception with no subclass (SQL-99 08000)
*/
public static final String CONNECTION_EXCEPTION_NO_SUBCLASS = "08000"; //$NON-NLS-1$
/**
* SQL-client unable to establish SQL-connection (SQL-99 08001)
*/
public static final String CONNECTION_EXCEPTION_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION = "08001"; //$NON-NLS-1$
/**
* Connection name in use (SQL-99 08002)
*/
public static final String CONNECTION_EXCEPTION_CONNECTION_NAME_IN_USE = "08002"; //$NON-NLS-1$
/**
* Connection does not exist (SQL-99 08003)
*/
public static final String CONNECTION_EXCEPTION_CONNECTION_DOES_NOT_EXIST = "08003"; //$NON-NLS-1$
/**
* SQL-server rejected establishment of SQL-connection (SQL-99 08004)
*/
public static final String CONNECTION_EXCEPTION_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION = "08004"; //$NON-NLS-1$
/**
* Connection failure (SQL-99 08006)
*/
public static final String CONNECTION_EXCEPTION_CONNECTION_FAILURE = "08006"; //$NON-NLS-1$
/**
* Transaction resolution unknown (SQL-99 08007)
*/
public static final String CONNECTION_EXCEPTION_TRANSACTION_RESOLUTION_UNKNOWN = "08007"; //$NON-NLS-1$
/**
* Connection is stale and should no longer be used. (08S01)
* <p>
* The SQLState subclass S01 is an implementation-specified condition and
* conforms to the subclass DataDirect uses for SocketExceptions.
*/
public static final String CONNECTION_EXCEPTION_STALE_CONNECTION = "08S01"; //$NON-NLS-1$
// Class 28 - invalid authorization specification
/**
* Identifies the SQLState class Invalid Authorization Specification (28).
*/
public static final SQLStateClass CLASS_INVALID_AUTHORIZATION_SPECIFICATION = new SQLStateClass(
"28"); //$NON-NLS-1$
/**
* Invalid authorization specification with no subclass (SQL-99 28000)
*/
public static final String INVALID_AUTHORIZATION_SPECIFICATION_NO_SUBCLASS = "28000"; //$NON-NLS-1$
// Class 38 - External Routine Exception (as defined by SQL spec):
/** External routine exception. This is the default unknown code */
public static final String DEFAULT = "38000"; //$NON-NLS-1$
public static final String SUCESS = "00000"; //$NON-NLS-1$
// Class 50 - Query execution errors
public static final SQLStateClass CLASS_USAGE_ERROR = new SQLStateClass("50"); //$NON-NLS-1$
/** General query execution error*/
public static final String USAGE_ERROR = "50000"; //$NON-NLS-1$
/** Error raised by ERROR instruction in virtual procedure.*/
public static final String VIRTUAL_PROCEDURE_ERROR = "50001"; //$NON-NLS-1$
private static final SQLStateClass[] stateClasses = {CLASS_USAGE_ERROR};
static {
CLASS_USAGE_ERROR.stateCodes.add(USAGE_ERROR);
CLASS_USAGE_ERROR.stateCodes.add(VIRTUAL_PROCEDURE_ERROR);
}
public static boolean isSystemErrorState(String sqlStateCode) {
return !isUsageErrorState(sqlStateCode);
}
public static boolean isUsageErrorState(String sqlStateCode) {
return belongsToClass(sqlStateCode, CLASS_USAGE_ERROR);
}
public static boolean belongsToClass(String sqlStateCode, SQLStateClass sqlStateClass) {
return sqlStateCode.startsWith(sqlStateClass.codeBeginsWith);
}
public static SQLStateClass getClass(String sqlStateCode) {
for (int i = 0; i < stateClasses.length; i++) {
if (stateClasses[i].containsSQLState(sqlStateCode)) {
return stateClasses[i];
}
}
return null;
}
public static final class SQLStateClass {
private String codeBeginsWith;
private Set<String> stateCodes = new HashSet<String>();
private SQLStateClass(String beginsWith) {
this.codeBeginsWith = beginsWith;
}
public boolean containsSQLState(String sqlState) {
return stateCodes.contains(sqlState);
}
}
}
}