/*
* $Id$
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons.connector.http;
import java.io.ByteArrayInputStream;
import java.io.PrintWriter;
import java.security.KeyStore;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
import org.jcoderz.commons.connector.ConnectorConfiguration;
import org.jcoderz.commons.connector.http.transport.ConnectorContext;
import org.jcoderz.commons.connector.http.transport.HttpClientConnection;
import org.jcoderz.commons.connector.http.transport.HttpClientConnectionImpl;
import org.jcoderz.commons.connector.http.transport.HttpConnectionException;
import org.jcoderz.commons.connector.http.transport.HttpConnectorEventListener;
import org.jcoderz.commons.connector.http.transport.HttpRequestResponseHeader;
import org.jcoderz.commons.types.Url;
import org.jcoderz.commons.util.Constants;
/**
* This class defines the managed connection maintained by the application
* server and triggered via the non-managed connection.
*
*/
public class HttpManagedConnectionImpl
implements ManagedConnection
{
/** Class name for use in logging. */
private static final String CLASSNAME
= HttpManagedConnectionImpl.class.getName();
/** Logger in use. */
private static final Logger logger
= Logger.getLogger(CLASSNAME);
/** Indicated if the managed connection is established. */
private boolean mConnectionAssociated = false;
/** Indicates if the managed connection is opened. */
private boolean mConnectionOpened = false;
/** The logger used for the implementation of the interface
ManagedConnection.*/
private PrintWriter mPrintWriter;
/** Object containing all app server references that must be notified
used for the implementation of the interface ManagedConnection. */
private final Set mEventListeners = new HashSet();
/** Connection handle associated with this ManagedConnection. */
private HttpConnectionImpl mConnectionHandle;
/** The factory which have created this object. */
private final HttpManagedConnectionFactoryImpl mManagedConnectionFactory;
/** The connection specification used to trigger the creation of
this physical connection. */
private HttpConnectionRequestInfo mConnectionRequestInfo;
/** Physical Connection */
private HttpClientConnection mConnection;
/** Flag indicates that the physical connection must be closed. */
private boolean mPhysicalCloseRequired = false;
/** URL the physical Connection will connect to. */
private Url mUrl;
private final ConnectorConfiguration mConfig;
private int mConnectTimeout;
private int mReadTimeout;
private String mKeyAlias;
private String mKeyAliasPassword;
private KeyStore mKeyStore = null;
private KeyStore mTrustStore = null;
private HttpConnectorEventListener mHttpEventListener;
private ConnectorContext mConnectorContext;
private HttpRequestResponseHeader mRequestResponseHeader;
/**
* Constructor.
* @param mcf the HttpManagedConnectionFactoryImpl associated with
* @param cri the ConnectionRequestInfo specifying the requested connection
*/
public HttpManagedConnectionImpl (
HttpManagedConnectionFactoryImpl mcf,
ConnectionRequestInfo cri)
{
final String methodName = "Constructor";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName, cri);
}
mConfig = ConfigurationFactory.getConfiguration();
// set the ManagedConnectionFactory creating this object
mManagedConnectionFactory = mcf;
// set the obtained ConnectionRequestInfo
mConnectionRequestInfo = (HttpConnectionRequestInfo) cri;
mConnectionAssociated = false;
mConnectionOpened = false;
mConnectionHandle = null;
if (mConnectionRequestInfo != null)
{
configure(mConnectionRequestInfo);
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/*
* Methods defined in interface
* javax.resource.spi.ManagedConnection
*
* void addConnectionEventListener
* (ConnectionEventListener listener)
* void associateConnection
* (java.lang.Object connection)
* void cleanup ()
* void destroy ()
* java.lang.Object getConnection (Subject subject,
* ConnectionRequestInfo info)
* LocalTransaction getLocalTransaction ()
* java.io.PrintWriter getLogWriter ()
* void setLogWriter (java.io.PrintWriter out)
* ManagedConnectionMetaData getMetaData ()
* javax.transaction.xa.XAResource getXAResource ()
* void removeConnectionEventListener
* (ConnectionEventListener listener
*/
/** {@inheritDoc} */
public void addConnectionEventListener (ConnectionEventListener listener)
{
mEventListeners.add(listener);
}
/** {@inheritDoc} */
public void associateConnection (Object connection)
{
final String methodName = "associateConnection";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
// the managed connection is already configured and the
// physical connection created anew if necessary
// - the check is obsolete
//checkIfDestroyed();
if (mConnectionHandle != null)
{
if (logger.isLoggable(Level.WARNING))
{
final String messageText = "connection still have a handle";
logger.warning(messageText);
}
mConnectionHandle.disassociateManagedConnection(true);
}
final HttpConnectionImpl usercon = (HttpConnectionImpl) connection;
usercon.associateManagedConnection(this);
mConnectionHandle = usercon;
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/** {@inheritDoc} */
public void cleanup ()
{
final String methodName = "cleanup";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
// if makes no difference if the physical connection is closed
// or not cause reusing the managed connection creates a new
// physical connection inside "configure" if necessary
// - the check is obsolete
//checkIfDestroyed();
// Invalidate the connection handle and
// release the reference.
if (mConnectionAssociated)
{
mConnectionHandle.disassociateManagedConnection(true);
mConnectionAssociated = false;
mConnectionHandle = null;
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/** {@inheritDoc} */
public void destroy ()
{
final String methodName = "destroy";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
// the container is not the only one allowed to close the physical
// connection - the managed connection close it itself in
// case of an "Connection : Close" attribute received in the
// response message
// - the check is obsolete
//checkIfDestroyed();
// trace message
if (logger.isLoggable(Level.FINE))
{
final String messageText
= "destroy PHYSICAL CONNECTION (" + this + ") to "
+ mConnectionRequestInfo;
logger.log(Level.FINE, messageText);
}
//
disconnect();
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/** {@inheritDoc} */
public Object getConnection (Subject subject, ConnectionRequestInfo cri)
{
final String methodName = "getConnection";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
mPhysicalCloseRequired = false;
mConnectionRequestInfo = (HttpConnectionRequestInfo) cri;
// There must not an association to a connection handle when this
// method is called.
if (mConnectionAssociated)
{
final String errorText
= "managed connection already associated to a connection handle";
throw new IllegalStateException(errorText);
}
// if not connected then configure and connect
if (!mConnectionOpened)
{
// trace message
if (logger.isLoggable(Level.FINE))
{
final String messageText
= "establishing NEW PHYSICAL CONNECTION (" + this + ") to "
+ mConnectionRequestInfo;
logger.fine(messageText);
}
//
if (mConnectionRequestInfo != null)
{
configure(mConnectionRequestInfo);
}
// we connect while sending....
}
else
{
// trace message
if (logger.isLoggable(Level.FINE))
{
final String messageText
= "reusing PHYSICAL CONNECTION (" + this + ") to "
+ mConnectionRequestInfo;
logger.fine(messageText);
}
//
}
mConnectionAssociated = true;
mConnectionHandle = new HttpConnectionImpl(this);
return mConnectionHandle;
}
/** {@inheritDoc} */
public LocalTransaction getLocalTransaction ()
throws ResourceException
{
final String messageText = "Local transaction not supported";
throw new NotSupportedException(messageText);
}
/** {@inheritDoc} */
public PrintWriter getLogWriter ()
{
return mPrintWriter;
}
/** {@inheritDoc} */
public void setLogWriter (PrintWriter out)
{
mPrintWriter = out;
}
/** {@inheritDoc} */
public ManagedConnectionMetaData getMetaData ()
{
// it is not specified that the physical connection have to be
// open whilst that call
final HttpManagedConnectionMetaData managedConnectionMetaData
= new HttpManagedConnectionMetaData(this);
return managedConnectionMetaData;
}
/** {@inheritDoc} */
public XAResource getXAResource ()
throws ResourceException
{
final String messageText = "XA transaction not supported";
throw new NotSupportedException(messageText);
}
/** {@inheritDoc} */
public void removeConnectionEventListener (ConnectionEventListener listener)
{
mEventListeners.remove(listener);
}
/*
* Implementation of interface
* javax.resource.spi.HttpManagedConnection
*
* finished.
*/
/**
* Performs a physical connection close.
*/
private void disconnect ()
{
final String methodName = "disconnect";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
// if "disconnect" is triggered by container side
// and the response contained a "Connection:Close"
// HTTP header the connection has been already
// closed by the Managed Connection
if (!mConnectionOpened && mPhysicalCloseRequired)
{
if (logger.isLoggable(Level.FINE))
{
final String messageText = "already disconnected (" + this + ")"
+ " due to \'Connection:Close\' in response";
logger.fine(messageText);
}
}
else
{
// close the physical socket connection
mConnection.closeConnection();
mConnectionOpened = false;
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/**
* Configures the physical connection.
* Uses configuration parameter from the
* {@link org.jcoderz.commons.connector.http.HttpConnectionSpec
* HttpConnectionSpec}.
* This method is called inside the
* {@link #HttpManagedConnection(HttpManagedConnectionFactoryImpl,
* ConnectionRequestInfo) constructor}
* and
* {@link #getConnection(Subject, ConnectionRequestInfo ) getConnection(..)}
* method.
*
* @param cri the Connection Request Info
*/
private final void configure (HttpConnectionRequestInfo cri)
{
final String methodName = "configure";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
String messageText;
String errorText;
if (mConnectionOpened)
{
messageText = "Unable to configure an already opened connection";
// trace message
if (logger.isLoggable(Level.WARNING))
{
logger.warning(messageText);
}
//
}
else
{
if (cri.getConnectionSpec() != null)
{
// set configuration parameters obtained in the cri
// for the connection
mUrl = cri.getConnectionSpec().getUrl();
mConnectTimeout = mConfig.getConnectTimeoutInMilliSeconds();
mReadTimeout = mConfig.getReadTimeoutInMilliSeconds();
mKeyAlias = mConfig.getSslKeyAlias();
mKeyAliasPassword = mConfig.getSslKeyAliasPassword();
//FIXME: mKeyStore = KeyStoreLocator.getKeyStore();
//FIXME: mTrustStore = KeyStoreLocator.getTrustStore();
if (mConnection == null)
{
// trace message
if (logger.isLoggable(Level.FINER))
{
messageText = "creating new HTTPConnection";
logger.log(Level.FINER, messageText);
}
//
mConnection = new HttpClientConnectionImpl();
}
mConnection.initSsl(
mKeyStore, mTrustStore, mKeyAlias, mKeyAliasPassword);
mConnection.establishConnection(
mUrl.toString(), mConnectTimeout, mReadTimeout);
}
else
{
errorText = "connection specification is not set";
throw new IllegalStateException(errorText);
}
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/**
* This method is used by the
* {@link org.jcoderz.commons.connector.http.HttpConnectionImpl
* HttpConnection Implementation} class to send and receive an HTTP
* request/response using the commons-httpclient library.
*
* @param message the message body to send via HTTP.
* @return the response received.
* @throws HttpConnectionException for all types of connection failures
*/
public byte[] sendAndReceive (byte[] message)
throws HttpConnectionException
{
final String methodName = "sendAndReceive(byte[])";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
// set the event listener for "requestSend" / "responseReceived"
mConnection.setEventListener(mHttpEventListener, mConnectorContext);
// add Content-Length and Connection to the request header
// will not be overwritten if already set
getRequestResponseHeader().addRequestHeader("Content-Length",
String.valueOf(message.length));
getRequestResponseHeader().addRequestHeader("Connection",
"Keep-Alive");
// set all given request header pairs
mConnection.setRequestResponseHeader(getRequestResponseHeader());
// set the given message body
mConnection.setRequestBody(new ByteArrayInputStream(message));
mConnectionOpened = true;
//execute the httpclient connection
mConnection.execute();
// obtain the response
final byte[] responseBytes = mConnection.getResponseBody();
if (isCloseConnectionRequired())
{
mPhysicalCloseRequired = true;
}
mConnection.releaseConnection();
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
return responseBytes;
}
/**
* Sets the header set for sending and validate whilst receiving.
*
* @param header header to set and to validate
*/
public void setRequestResponseHeader (HttpRequestResponseHeader header)
{
mRequestResponseHeader = header;
}
/**
* Sets the event listener used for SLA logs.
* This listener will be set at the
*
* @param listener the listener to set
* @param context the context to set
*/
public void setEventListener (HttpConnectorEventListener listener,
ConnectorContext context)
{
mHttpEventListener = listener;
mConnectorContext = context;
}
/**
* This method is used by the
* {@link HttpConnectionHelper HttpConnectionHelper}
* class to disassociate the connection handle
* from this HttpManagedConnection instance.
*
* @param conn the connection handle to be disassociated
* @throws javax.resource.spi.IllegalStateException if the connection handle
* to disassociate is not associated
*/
void disassociateConnection (HttpConnectionImpl conn)
throws IllegalStateException
{
final String methodName = "disassociateConnection";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
if (conn != mConnectionHandle)
{
final String errorText = "Connection handle was not created by "
+ "THIS HttpManagedConnection " + conn;
final IllegalStateException ise = new IllegalStateException(errorText);
// trace message
if (logger.isLoggable(Level.FINE))
{
logger.log(Level.FINE, errorText);
}
if (logger.isLoggable(Level.FINER))
{
logger.throwing(CLASSNAME, methodName, ise);
}
//
throw ise;
}
mConnectionAssociated = false;
mConnectionHandle = null;
if (mPhysicalCloseRequired)
{
disconnect();
}
else
{
mConnection.releaseConnection();
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/**
* Gets the connection specification used to trigger the creation
* of this physical connection.
*
* @return HttpConnectionRequestInfo - the requested object specifying
* the requested connection
*/
ConnectionRequestInfo getConnectionRequestInfo ()
{
return mConnectionRequestInfo;
}
/**
* Notify the registered set of listeners when an application component
* closes a connection handle. The application server uses this connection
* close event to make a decision on whether or not to put this
* HttpManagedConnection instance back into the connection pool.
*
* @param connectionHandle the connection handle performing a close
*/
void notifyAboutConnectionClosed (
HttpConnectionImpl connectionHandle)
{
final String methodName = "notifyAboutConnectionClosed";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
if (mConnectionAssociated)
{
if (logger.isLoggable(Level.WARNING))
{
final String messageText
= "HttpManagedConnection SHOULD have disassociated before";
logger.warning(messageText);
}
mConnectionAssociated = false;
mConnectionHandle = null;
}
final ConnectionEvent ce = new ConnectionEvent(
this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(connectionHandle);
final Iterator it = mEventListeners.iterator();
while (it.hasNext())
{
final ConnectionEventListener listener
= (ConnectionEventListener) it.next();
listener.connectionClosed(ce);
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/**
* Notify the registered listeners of the occurrence of a physical
* connection-related error. The event notification happens just before
* a resource adapter throws an exception to the application component
* using the connection handle.
* The connectionErrorOccurred method indicates that the associated
* HttpManagedConnection instance is now invalid and unusable.
* The application server handles the connection error event
* notification by initiating application server-specific cleanup
* (for example, removing HttpManagedConnection instance from the
* connection pool) and then calling HttpManagedConnection.destroy
* method to destroy the physical connection.
*
* @param e the exception indication a connection error
* @param connectionHandle the connection handle notifying the
* connection error
*/
void notifyAboutConnectionErrorOccurred (
Exception e,
HttpConnectionImpl connectionHandle)
{
final String methodName = "notifyAboutConnectionErrorOccurred";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName);
}
if (mConnectionAssociated)
{
if (logger.isLoggable(Level.WARNING))
{
final String messageText
= "HttpManagedConnection SHOULD have disassociated before";
logger.warning(messageText);
}
}
final ConnectionEvent ce = new ConnectionEvent(
this, ConnectionEvent.CONNECTION_ERROR_OCCURRED, e);
if (connectionHandle != null)
{
ce.setConnectionHandle(connectionHandle);
}
final Iterator it = mEventListeners.iterator();
while (it.hasNext())
{
final ConnectionEventListener listener
= (ConnectionEventListener) it.next();
listener.connectionErrorOccurred(ce);
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName);
}
}
/**
* Returns true if the underlying physical connection is currently open.
*
* @return boolean - true if connection is open, else false
*/
boolean isOpen ()
{
return mConnectionOpened;
}
/**
* Checks the response header if a physical connection close is required.
*/
private boolean isCloseConnectionRequired ()
{
boolean result = false;
final String connectionValue
= mConnection.getResponseHeader("Connection");
if (connectionValue != null
&& connectionValue.toLowerCase(
Constants.SYSTEM_LOCALE).equals("close"))
{
logger.fine("\'Connection\' attribute in response header is \'"
+ connectionValue
+ "\' physical connection will be closed");
result = true;
}
return result;
}
protected Url getUrl ()
{
return mUrl;
}
/**
* Gets the managed connection factory of this managed connection.
*
* @return ManagedPaymentProtocolConnectionFactory the factory in use
*/
public HttpManagedConnectionFactoryImpl getManagedConnectionFactory ()
{
return mManagedConnectionFactory;
}
private HttpRequestResponseHeader getRequestResponseHeader ()
{
if (mRequestResponseHeader == null)
{
mRequestResponseHeader = new HttpRequestResponseHeader();
}
return mRequestResponseHeader;
}
/**
* Gets the string representation of the HttpManagedConnection.
*
* @return the string representation
*/
public String toString ()
{
final String result
= "HttpManagedConnectionImpl hashCode()=" + hashCode();
return result;
}
}