
com.legstar.csok.client.CicsSocket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of legstar-distribution
Show all versions of legstar-distribution
Used to create a single distribution for the entire LegStar project.
The newest version!
/*******************************************************************************
* Copyright (c) 2010 LegSem.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* LegSem - initial API and implementation
******************************************************************************/
package com.legstar.csok.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.legstar.codec.HostCodec;
import com.legstar.messaging.ConnectionException;
import com.legstar.messaging.HeaderPartException;
import com.legstar.messaging.HostMessageFormatException;
import com.legstar.messaging.LegStarConnection;
import com.legstar.messaging.LegStarMessage;
import com.legstar.messaging.LegStarRequest;
import com.legstar.messaging.RequestException;
/**
* Client side CICS Socket connectivity. This class provides the core methods to
* connect to CICS over sockets, send requests, receive results, etc...
*/
public class CicsSocket implements LegStarConnection {
/**
* Length of the Transaction Initial Message expected by the IBM CICS Socket
* listener.
*/
private static final int CIM_LEN = 35;
/** Size of the CICS USER ID. */
private static final int CICS_USERID_LEN = 8;
/** Size of the CICS Password. */
private static final int CICS_PWD_LEN = 8;
/** Size of connection identifier. */
private static final int CONNECTION_ID_LEN = 16;
/** Size of message type. */
private static final int MSG_TYPE_LEN = 9;
/** Size of header preceding any reply from host. TODO make public. */
private static final int REPLY_HDR_LEN = 9;
/** Size of trace option. */
private static final int TRACE_LEN = 1;
/**
* Maximum size of the host reply for acknowledgments and error reports.
* TODO make public. TODO make public.
*/
private static final int MAX_PROT_REPLY_LEN = 266;
/** CIM eye catcher. */
private static final String CIM_EYE_CATCHER = "SK";
/** Identifies execution requests. TODO make public. */
private static final String EXEC_REQUEST_EC = "LSOKEXEC";
/** Identifies probes (is alive ). */
private static final String PROBE_REQUEST_EC = "LSOKPROB";
/** Reply eye catcher for acknowledgements. TODO make public. */
private static final String REPLY_ACK_MSG_EC = "LSOKACK0";
/** Reply eye catcher for errors. TODO make public. */
private static final String REPLY_ERROR_MSG_EC = "LSOKERR0";
/** Eye catcher for data messages. TODO make public. */
private static final String DATA_MSG_EC = "LSOKDATA";
/** Processing instructions for UOW handling. */
private static final String UOW_MSG_EC = "LSOKUOWC";
/** UOW processing msg eye catcher length. */
private static final int UOW_MSG_EC_LEN = 8;
/** UOW command length. */
private static final int UOW_COMMAND_LEN = 8;
/** Processing instructions for UOW commit. */
public static final String UOW_COMMIT = "Commit";
/** Processing instructions for UOW rollback. */
public static final String UOW_ROLLBACK = "Rollback";
/** Processing instructions for UOW keep. */
public static final String UOW_KEEP = "Keep";
/** General protocol violation reply message. */
public static final String PROTOCOL_ERROR_MSG = "Invalid or unexpected reply from host.";
/** TCPIP Socket connection to a CICS region. */
private Socket mClientSocket = null;
/** An identifier for this connection. */
private String mConnectionID;
/** Host CICS Socket endpoint. */
private CicsSocketEndpoint mCicsSocketEndpoint;
/**
* This host character set is used for the protocol elements which are
* checked by the LegStar host programs. Because these target host programs
* are compiled with a fixed charset, it might be different from the actual
* user data character set.
*/
private String mHostProtocolCharset;
/** last time this connection was used. */
private long _lastUsedTime = -1;
/** true if connection opened. */
private boolean _isOpen;
/** Logger. */
private final Log _log = LogFactory.getLog(CicsSocket.class);
/**
* A CICS Socket exists for a target CICS region, a given CICS Socket
* listener and a particular User ID to use for authentication and
* impersonation. Observe that no password is stored in this class for
* security reasons.
*
* @param connectionID an identifier for this connection
* @param cicsSocketEndpoint CICS Socket endpoint
*/
public CicsSocket(final String connectionID,
final CicsSocketEndpoint cicsSocketEndpoint) {
mConnectionID = connectionID;
mCicsSocketEndpoint = cicsSocketEndpoint;
mHostProtocolCharset = HostCodec.HEADER_CODE_PAGE;
}
/**
* Connect to a CICS IBM Listener passing credentials and a client initial
* message. The reply must be an acknowldgement otherwise it is an error
* message returned from the host.
*
* @param cicsPassword credentials for security exist
* @throws ConnectionException if connection fails
*/
public void connect(final String cicsPassword) throws ConnectionException {
String password;
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID
+ " Attempting connection. Host:"
+ mCicsSocketEndpoint.toString());
}
try {
mClientSocket = new Socket();
mClientSocket.connect(
new InetSocketAddress(InetAddress
.getByName(mCicsSocketEndpoint.getHostIPAddress()),
mCicsSocketEndpoint.getHostIPPort()),
getCicsSocketEndpoint().getConnectTimeout());
mClientSocket.setSoTimeout(getCicsSocketEndpoint()
.getReceiveTimeout());
/*
* In order to optimize memory allocation, this client program sends
* message parts to server in 2 different sends. If we don t disable
* Nagle, there is an unacceptable delay in the server
* acknowldgement of the first send.
*/
mClientSocket.setTcpNoDelay(true);
/*
* In an RPC mode, there is no reason to wait for additional data
* when a close sequence is initiated (we wouldn't know what to do
* with that data anyway.
*/
mClientSocket.setSoLinger(false, 0);
/* If a password is not passed, use the one from configuration */
if (cicsPassword == null || cicsPassword.length() == 0) {
password = mCicsSocketEndpoint.getHostPassword();
} else {
password = cicsPassword;
}
OutputStream out = mClientSocket.getOutputStream();
out.write(formatCIM(mCicsSocketEndpoint.getHostUserID(), password,
mConnectionID, mCicsSocketEndpoint.isHostTraceMode(),
mHostProtocolCharset));
recvConnectionAck();
_isOpen = true;
_lastUsedTime = System.currentTimeMillis();
} catch (UnknownHostException e) {
throw (new ConnectionException(e));
} catch (IOException e) {
throw (new ConnectionException(e));
} catch (RequestException e) {
throw (new ConnectionException(e));
}
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Connected.");
}
}
/**
* This method probes a socket for reusability. A socket might be reusable
* (which is efficient). Unfortunatly, there are no socket methods that
* reliably tells you if socket is usable or not.
*
* @param cicsPassword host password if it is not stored in configuration
* file
* @throws ConnectionException if connection fails
* */
public void connectReuse(final String cicsPassword)
throws ConnectionException {
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Attempting reuse.");
}
/* If socket is reusable just return. */
if (mClientSocket != null && mClientSocket.isConnected()
&& isServerAlive()) {
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID
+ " Socket will be reused.");
}
_lastUsedTime = System.currentTimeMillis();
return;
}
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Socket not reusable.");
}
/* Socket is not reusable, fallback to standard connect. */
connect(cicsPassword);
}
/**
* After initial connect, the host should reply with an ack plus a bunch of
* attributes used to correlate this connection with host events.
*
* @throws RequestException if ack cannot be received
*/
private void recvConnectionAck() throws RequestException {
String ackString = null;
try {
InputStream in = mClientSocket.getInputStream();
ackString = getCharsFromHost(in, mHostProtocolCharset,
MAX_PROT_REPLY_LEN);
if (ackString == null || ackString.length() == 0) {
throw (new RequestException("No response from host."));
}
/* If this is not a valid ACK, it could be an error report */
if (REPLY_ACK_MSG_EC.compareTo(ackString.substring(0,
REPLY_ACK_MSG_EC.length())) != 0) {
/*
* Sanity check for characters being displayable. We expect the
* host error reply to start with an error code in uppercase
* characters.
*/
if (Character.getType(ackString.charAt(0)) == Character.UPPERCASE_LETTER) {
throw (new RequestException(ackString));
} else {
throw (new RequestException(
"Unrecognized response from host."));
}
}
} catch (IOException e) {
throw (new RequestException(e));
}
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID
+ " Connection Ack received." + ackString);
}
}
/**
* A request is serialized as a message type, then a header message part
* followed by data message parts.
*
* @param request the request to be serviced
* @throws RequestException if send fails
*/
public void sendRequest(final LegStarRequest request)
throws RequestException {
if (_log.isDebugEnabled()) {
try {
_log.debug("Sending Request:"
+ request.getID()
+ " on Connection:"
+ mConnectionID
+ " "
+ request.getRequestMessage().getHeaderPart()
.getJsonString() + '.');
} catch (HeaderPartException e) {
throw new RequestException(e);
}
}
CicsSocketOutputBuffer outputBuffer = null;
try {
/* Buffer output to reduce the number of individual socket sends. */
outputBuffer = new CicsSocketOutputBuffer(
mClientSocket.getOutputStream(),
mClientSocket.getSendBufferSize());
/* Send message type signaling a request */
outputBuffer.write(formatMessageType(EXEC_REQUEST_EC,
mHostProtocolCharset));
/* Serialize the request message on the stream */
outputBuffer.write(request.getRequestMessage().sendToHost());
/* Send any remaining data in the buffer */
outputBuffer.flush();
} catch (IOException e) {
throw (new RequestException(e));
} catch (HostMessageFormatException e) {
throw (new RequestException(e));
}
_lastUsedTime = System.currentTimeMillis();
if (_log.isDebugEnabled()) {
_log.debug("Request:" + request.getID() + " on Connection:"
+ mConnectionID + " message request sent.");
}
}
/**
* A response is serialized as a header message part followed by data
* message parts. This method creates a response message for the request.
*
* @param request the request being serviced
* @throws RequestException if receive fails
*/
public void recvResponse(final LegStarRequest request)
throws RequestException {
if (_log.isDebugEnabled()) {
_log.debug("Receiving response for Request:" + request.getID()
+ " on Connection:" + mConnectionID + '.');
}
try {
/* First get the eye catcher portion of the reply */
InputStream respStream = mClientSocket.getInputStream();
String msgType = recvMessageType(respStream);
/* Check if this is a valid reply or an error reply */
if (DATA_MSG_EC
.compareTo(msgType.substring(0, DATA_MSG_EC.length())) != 0) {
recvError(msgType, respStream);
}
/* Deserialize the rest of the stream into a response message */
LegStarMessage reponseMessage = new LegStarMessage();
reponseMessage.recvFromHost(respStream);
request.setResponseMessage(reponseMessage);
} catch (IOException e) {
throw (new RequestException(e));
} catch (HeaderPartException e) {
throw (new RequestException(e));
} catch (HostMessageFormatException e) {
throw (new RequestException(e));
}
_lastUsedTime = System.currentTimeMillis();
if (_log.isDebugEnabled()) {
_log.debug("Request:" + request.getID() + " on Connection:"
+ mConnectionID + " response received.");
}
}
/**
* All replies must start with a fixed size message type.
*
* @param in an opened input stream on the host
* @return the header element
* @throws RequestException if header element cannot be recovered
*/
private String recvMessageType(final InputStream in)
throws RequestException {
String msgType = getCharsFromHost(in, mHostProtocolCharset,
REPLY_HDR_LEN);
if (msgType == null || msgType.length() < REPLY_HDR_LEN) {
throw (new RequestException(PROTOCOL_ERROR_MSG
+ " message type received: " + msgType));
}
return msgType;
}
/**
* When the reply does not present the expected header, it might contain an
* error report.
*
* @param msgType the header received
* @param in an opened input stream on the host
* @throws RequestException in all cases
*/
private void recvError(final String msgType, final InputStream in)
throws RequestException {
if (REPLY_ERROR_MSG_EC.compareTo(msgType.substring(0,
REPLY_ERROR_MSG_EC.length())) != 0) {
/* Consider this is a system error message */
String errString = getCharsFromHost(in, mHostProtocolCharset,
MAX_PROT_REPLY_LEN);
throw (new RequestException(msgType + errString));
} else {
/* Get the error message content */
String errString = getCharsFromHost(in, mHostProtocolCharset,
MAX_PROT_REPLY_LEN);
throw (new RequestException(errString.trim()));
}
}
/**
* Terminates a connection with the host.
*
* @throws RequestException if a failure is detected
*/
public void close() throws RequestException {
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Attempting to close.");
}
if (mClientSocket == null) {
return;
}
if (!mClientSocket.isClosed()) {
try {
mClientSocket.close();
} catch (IOException e) {
throw (new RequestException(e));
}
}
mClientSocket = null;
_isOpen = false;
_lastUsedTime = System.currentTimeMillis();
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Closed.");
}
}
/**
* Request Unit Of Work commit.
*
* @throws RequestException if a failure is detected
*/
public void commitUOW() throws RequestException {
processUOW(UOW_COMMIT);
}
/**
* Request Unit Of Work continuation.
*
* @throws RequestException if a failure is detected
*/
public void keepUOW() throws RequestException {
processUOW(UOW_KEEP);
}
/**
* Request Unit Of Work rollback.
*
* @throws RequestException if a failure is detected
*/
public void rollbackUOW() throws RequestException {
processUOW(UOW_ROLLBACK);
}
/**
* Instruct host on Unit Of Work processing and wait for Ack.
*
* @param command the unit of work command (commit, rollback or keep)
* @throws RequestException if a failure is detected
*/
private void processUOW(final String command) throws RequestException {
if (!isOpen()) {
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Attempting to "
+ command
+ " unit of work on a closed connection. Ignoring.");
}
return;
}
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Attempting to "
+ command + " unit of work.");
}
try {
OutputStream out = mClientSocket.getOutputStream();
out.write(formatUOW(command, mHostProtocolCharset));
receiveAck();
} catch (IOException e) {
throw (new RequestException(e));
}
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " " + command
+ " success.");
}
}
/**
* Used to check if server is still alive.
*
* @return true if server transaction is alive
*/
private boolean isServerAlive() {
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID
+ " Attempting to probe server.");
}
try {
OutputStream out = mClientSocket.getOutputStream();
out.write(formatProbe(mHostProtocolCharset));
receiveAck();
return true;
} catch (IOException e) {
return false;
} catch (RequestException e) {
return false;
}
}
/**
* Expecting an aknowlegement coming back from the host in reply to
* something we just sent.
*
* @throws RequestException if ack cannot be received
*/
private void receiveAck() throws RequestException {
try {
InputStream in = mClientSocket.getInputStream();
String msgType = recvMessageType(in);
/* If this is not a valid ACK, it could be an error report */
if (REPLY_ACK_MSG_EC.compareTo(msgType.substring(0,
REPLY_ACK_MSG_EC.length())) != 0) {
recvError(msgType, in);
}
} catch (IOException e) {
throw (new RequestException(e));
}
if (_log.isDebugEnabled()) {
_log.debug("Connection:" + mConnectionID + " Ack received.");
}
}
/**
* Receives character data from host and convert it from character set. It
* is assumed the maximum size to receive is small and is unlikely to get
* chunked.
*
* @param in an it stream from a socket connection to a host
* @param charset the host character set
* @param maxSize the largest expected size
* @return the result string
* @throws RequestException if receiving fails
*/
private static String getCharsFromHost(final InputStream in,
final String charset, final int maxSize) throws RequestException {
String str = null;
try {
byte[] buffer = new byte[maxSize];
int size = in.read(buffer);
if (size == -1) {
return null;
}
str = new String(buffer, 0, size, charset);
} catch (UnsupportedEncodingException e) {
throw (new RequestException(e));
} catch (IOException e) {
throw (new RequestException(e));
}
return str;
}
/**
* The Client Initial Message (CIM) is data that the IBM CICS Socket
* listener will pass to security exist and then to the server transaction.
* It is used by the LegStar protocol to pass data that remains valid for
* the entire duration of a connection.
*
* @param cicsUserID the CICS User ID to authenticate/impersonate
* @param cicsPassword the CICS User credentials
* @param connectionID A unique string representing this connection
* @param trace enable trace mode
* @param charset the host character set
* @return the formatted CIM
* @throws UnsupportedEncodingException if initial message formatting fails
*/
public static byte[] formatCIM(final String cicsUserID,
final String cicsPassword, final String connectionID,
final boolean trace, final String charset)
throws UnsupportedEncodingException {
byte[] cicsCIM = new byte[CIM_LEN];
int pos = 0;
HostCodec.toHostBytes(cicsUserID, cicsCIM, pos, CICS_USERID_LEN,
charset);
pos += CICS_USERID_LEN;
HostCodec
.toHostBytes(cicsPassword, cicsCIM, pos, CICS_PWD_LEN, charset);
pos += CICS_PWD_LEN;
HostCodec.toHostBytes(connectionID, cicsCIM, pos, CONNECTION_ID_LEN,
charset);
pos += CONNECTION_ID_LEN;
if (trace) {
HostCodec.toHostBytes("1", cicsCIM, pos, TRACE_LEN, charset);
} else {
HostCodec.toHostBytes("0", cicsCIM, pos, TRACE_LEN, charset);
}
pos += TRACE_LEN;
HostCodec.toHostBytes(CIM_EYE_CATCHER, cicsCIM, pos,
CIM_EYE_CATCHER.length(), charset);
pos += CIM_EYE_CATCHER.length();
return cicsCIM;
}
/**
* Formats the message type, ready for serialization.
*
* @param messageType message part type
* @param charset the host character set
* @return the serialized message type
* @throws UnsupportedEncodingException if formatting fails
*/
public static byte[] formatMessageType(final String messageType,
final String charset) throws UnsupportedEncodingException {
byte[] aMTBytes = new byte[MSG_TYPE_LEN];
int pos = 0;
HostCodec
.toHostBytes(messageType, aMTBytes, pos, MSG_TYPE_LEN, charset);
return aMTBytes;
}
/**
* Formats the UOW command instructing host on how to deal with the current
* unit of work.
*
* @param command the UOW processing command
* @param charset the host character set
* @return the serialized header
* @throws UnsupportedEncodingException if formatting fails
*/
public static byte[] formatUOW(final String command, final String charset)
throws UnsupportedEncodingException {
byte[] aUOWBytes = new byte[UOW_MSG_EC_LEN + 1 + UOW_COMMAND_LEN + 1];
int pos = 0;
HostCodec.toHostBytes(UOW_MSG_EC, aUOWBytes, pos, UOW_MSG_EC_LEN,
charset);
pos += (UOW_MSG_EC_LEN + 1);
HostCodec.toHostBytes(command, aUOWBytes, pos, command.length(),
charset);
return aUOWBytes;
}
/**
* Formats the probe request.
*
* @param charset the host character set
* @return the serialized header
* @throws UnsupportedEncodingException if formatting fails
*/
public static byte[] formatProbe(final String charset)
throws UnsupportedEncodingException {
byte[] probeBytes = new byte[PROBE_REQUEST_EC.length() + 1];
int pos = 0;
HostCodec.toHostBytes(PROBE_REQUEST_EC, probeBytes, pos,
PROBE_REQUEST_EC.length(), charset);
return probeBytes;
}
/**
* @return the current socket connection
*/
public Socket getClientSocket() {
return mClientSocket;
}
/**
* @param clientSocket a socket connection to a CICS region
*/
public void setClientSocket(final Socket clientSocket) {
mClientSocket = clientSocket;
}
/**
* @return the CICS socket endpoint
*/
public CicsSocketEndpoint getCicsSocketEndpoint() {
return mCicsSocketEndpoint;
}
/**
* @param cicsSocketEndpoint the CICS socket endpoint to set
*/
public void setCicsSocketEndpoint(
final CicsSocketEndpoint cicsSocketEndpoint) {
mCicsSocketEndpoint = cicsSocketEndpoint;
}
/**
* @return the identifier for this connection
*/
public String getConnectionID() {
return mConnectionID;
}
/**
* @param connectionID an identifier for this connection to set
*/
public void setConnectionID(final String connectionID) {
mConnectionID = connectionID;
}
/**
* (non-Javadoc).
*
* @see com.legstar.messaging.Connection#getConnectTimeout() {@inheritDoc}
*/
public long getConnectTimeout() {
return getCicsSocketEndpoint().getConnectTimeout();
}
/**
* (non-Javadoc).
*
* @see com.legstar.messaging.Connection#getReceiveTimeout() {@inheritDoc}
*/
public long getReceiveTimeout() {
return getCicsSocketEndpoint().getReceiveTimeout();
}
/** {@inheritDoc} */
public boolean isOpen() {
return _isOpen;
}
/** {@inheritDoc} */
public long getLastUsedTime() {
return _lastUsedTime;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy