src.com.ibm.as400.micro.MEServer Maven / Gradle / Ivy
Show all versions of jt400-jdk8 Show documentation
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: MEServer.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2001 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.micro;
import java.io.*;
import java.net.*;
import java.util.*;
import java.sql.*;
import java.util.Date;
import java.util.Locale;
import java.beans.PropertyVetoException;
import com.ibm.as400.data.*;
import com.ibm.as400.access.*;
/**
* The MEServer class is used to fulfill requests from programs
* that are using the ToolboxME for i5/OS jar file. The MEServer is responsible for
* creating and invoking methods on Toolbox objects on behalf of the
* program. The MEServer is intended for use when the client
* is running on a Tier 0 wireless device.
*
* If there is already a server active on the specified
* port, then an MEServer will not be started.
*
*
The MEServer can be run as an
* application, as follows:
*
*
*
* java com.ibm.as400.micro.MEServer [ options ]
*
*
*
* Options:
*
*
* -pcml
pcml doc1 [;pcml doc2;...]
* -
* Specifies the PCML document to pre-load and parse. This option may be abbreviated -pc.
*
* The PCML document will be loaded from the classpath. The classpath will first be searched
* for a serialized document. If a serialized document is not found, the classpath will be searched
* for a PCML source file.
*
* Using this option allows the MEServer to load, parse, and cache the ProgramCallDocument before
* it is used. When a ToolboxME ProgramCall request is made, the MEServer can than
* use the cached ProgramCallDocument to call the program.
*
* If this option is not specified, the PCML will be loaded when the ToolboxME ProgramCall
* request is made. The PCML will be loaded during runtime and will decrease performance.
*
* This option may be abbreviated
* -pc
.
*
*
* -port
port
* -
* Specifies the port to use for accepting connections from clients. This option may be abbreviated -po.
* The default port is 3470. This option may be abbreviated
*
-po
.
*
*
* -verbose
[true|false]
* -
* Specifies whether to print status and connection
* information to System.out. This option may be abbreviated
*
-v
.
*
*
* -help
* -
* Prints usage information to System.out. This option may be abbreviated
*
-h
or -?
. The default is not to print usage
* information.
*
*
*
*
* Example usage:
*
*
Start the MEServer from the command line as follows:
*
* java com.ibm.as400.micro.MEServer
*
**/
public class MEServer implements Runnable
{
// MRI
private static final String ME_CONNECTION_ACCEPTED_ = ResourceBundleLoader_m.getText("ME_CONNECTION_ACCEPTED");
// Private data.
private Socket socket_;
private MicroDataInputStream input_;
private MicroDataOutputStream output_;
private com.ibm.as400.access.AS400 system_;
private com.ibm.as400.access.CommandCall cc_;
private Connection connection_;
private Service service_;
// increment datastream level whenever a client/server datastream change is made.
private static final int DATASTREAM_LEVEL = 0;
private static PrintStream verbose_ = System.out;
private static boolean verboseState_ = false;
// Table of ProgramCallDocument objects.
private static final Hashtable registeredDocuments_ = new Hashtable();
private static int port_;
private static int threadIndex_ = 0;
private static final Vector expectedOptions_ = new Vector();
private static final Hashtable shortcuts_ = new Hashtable();
// These aren't static since they belong to a specific MEServer thread.
private int nextTransactionID_ = 0x0000;
private Hashtable cachedJDBCTransactions_ = new Hashtable(); // Table of ResultSets
static
{
// Expected options for the ProxyServer application.
expectedOptions_.addElement("-port");
expectedOptions_.addElement("-pcml");
expectedOptions_.addElement("-verbose");
expectedOptions_.addElement("-help");
// Shortcuts for the ProxyServer application.
// Note: These are also listed in usage().
shortcuts_.put("-po", "-port");
shortcuts_.put("-pc", "-pcml");
shortcuts_.put("-v", "-verbose");
shortcuts_.put("-h", "-help");
shortcuts_.put("-?", "-help");
}
/**
* Constructs an MEServer object.
**/
private MEServer(Socket s) throws IOException
{
socket_ = s;
input_ = new MicroDataInputStream( new BufferedInputStream( socket_.getInputStream() ) );
output_ = new MicroDataOutputStream( new BufferedOutputStream( socket_.getOutputStream() ) );
}
/**
* Cache the SQL Result Set.
**/
private int cacheTransaction(ResultSet rs)
{
Integer key = new Integer(nextTransactionID_++);
cachedJDBCTransactions_.put(key, rs);
return key.intValue();
}
// From com.ibm.as400.access.BinaryConverter
static char[] byteArrayToCharArray(byte[] byteValue)
{
if (byteValue == null)
return null;
char[] charValue = new char[byteValue.length / 2];
int inPos = 0;
int outPos = 0;
while (inPos < byteValue.length)
{
charValue[outPos++] = (char)(((byteValue[inPos++] & 0xFF) << 8) + (byteValue[inPos++] & 0xFF));
}
return charValue;
}
/**
* Close the streams and socket associated with this MEServer.
**/
private void close()
{
try
{
if (connection_ != null)
connection_.close();
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error closing MEServer SQL Connection.", e);
}
try
{
if (system_ != null)
{
system_.disconnectAllServices();
system_ = null;
}
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error disconnecting all services on MEServer.", e);
}
system_ = null;
try
{
if (input_ != null)
{
input_.in_.close();
input_ = null;
}
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error closing MEServer input stream.", e);
}
try
{
if (output_ != null)
{
output_.out_.close();
output_ = null;
}
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error closing MEServer output stream.", e);
}
try
{
if (socket_ != null)
{
socket_.close();
socket_ = null;
if (verboseState_)
verbose_.println ( ResourceBundleLoader_m.getText ("ME_CONNECTION_CLOSED", Thread.currentThread().getName() ) );
}
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error closing MEServer socket.", e);
}
try
{
for (Enumeration e = cachedJDBCTransactions_.keys() ; e.hasMoreElements() ;)
{
((AS400JDBCResultSet) cachedJDBCTransactions_.get(e)).close();
cachedJDBCTransactions_.remove(e);
}
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error closing SQL Result Set.", e);
}
}
// Unscramble some bytes.
private static byte[] decode(byte[] adder, byte[] mask, byte[] bytes)
{
int length = bytes.length;
byte[] buf = new byte[length];
for (int i = 0; i < length; ++i)
{
buf[i] = (byte)(mask[i % mask.length] ^ bytes[i]);
}
for (int i = 0; i < length; ++i)
{
buf[i] = (byte)(buf[i] - adder[i % adder.length]);
}
return buf;
}
/**
* Receives the command call datastream from the client, runs the command, and sends a reply.
*
* Datastream request format:
* String - command string including CL command name and all of its parameters
*
* Datastream reply format:
* int - number of messages; 0 if there are no messages.
* String[] - messages, consisting of CPF ID and message text
**/
private void doCommandCall() throws ErrorCompletingRequestException, InterruptedException, IOException
{
String command = input_.readUTF();
if (cc_ == null)
cc_ = new CommandCall(system_);
boolean retVal = false;
try
{
retVal = cc_.run(command);
}
catch (PropertyVetoException pve)
{
if (Trace.isTraceOn())
Trace.log(Trace.ERROR, "PropertyVetoException on run(command)", pve);
}
catch (Exception e)
{
output_.writeInt(MEConstants.EXCEPTION_OCCURRED);
output_.flush();
sendException(e);
return;
}
int numReplies = 0;
AS400Message[] messages = cc_.getMessageList();
// Only failed messages are sent back to the client.
if (!retVal)
numReplies = messages.length;
if (Trace.isTraceOn())
Trace.log(Trace.PROXY, "Number of messages returned from ToolboxME CommandCall: " + numReplies);
output_.writeInt(numReplies);
output_.flush();
for (int i=0; i 7) // time to grab another long
{
feeder = r.nextLong();
feederIndex = 0;
}
buf[i] = (byte)(0xFF & (feeder >> 8*feederIndex++));
}
return buf;
}
/**
* Parses the command line arguments and sets the properties accordingly.
*
* @param args The command line arguments.
* @return true if the combination of command line arguments is valid, false otherwise.
**/
private static boolean parseArgs (String[] args)
{
String optionValue;
CommandLineArguments cla = new CommandLineArguments (args, expectedOptions_ , shortcuts_);
if (cla.getOptionValue ("-help") != null)
return false;
optionValue = cla.getOptionValue("-verbose");
if (optionValue != null)
{
if ((optionValue.length () == 0) || (optionValue.equalsIgnoreCase ("true")))
verboseState_ = true;
else if (optionValue.equalsIgnoreCase ("false"))
verboseState_ = false;
else
throw new IllegalArgumentException( ResourceBundleLoader_m.getText("ME_OPTION_VALUE_NOT_VALID", new String[] { "verbose", optionValue}) );
}
optionValue = cla.getOptionValue ("-pcml");
if (optionValue != null)
{
StringTokenizer msc = new StringTokenizer(optionValue, ";");
while (msc.hasMoreTokens())
{
try
{
getPCMLDocument(msc.nextToken());
}
catch (PcmlException pe)
{
verbose_.println( ResourceBundleLoader_m.getText("ME_PCML_ERROR") );
if (verboseState_)
verbose_.println( pe.getMessage() );
if (Trace.isTraceOn())
Trace.log(Trace.ERROR, pe);
}
}
}
optionValue = cla.getOptionValue ("-port");
if (optionValue != null)
{
if (optionValue.length() > 0)
port_ = Integer.parseInt (optionValue);
}
else
port_ = MEConstants.ME_SERVER_PORT;
// Extra options.
Enumeration options = cla.getExtraOptions ();
while (options.hasMoreElements ())
{
String extraOption = options.nextElement().toString();
verbose_.println (ResourceBundleLoader_m.getText ("ME_OPTION_NOT_VALID", extraOption));
}
return true;
}
// Get clear password bytes back.
private static String resolve(byte[] info)
{
byte[] adder = new byte[MEConstants.ADDER_LENGTH];
System.arraycopy(info, 0, adder, 0, MEConstants.ADDER_LENGTH);
byte[] mask = new byte[MEConstants.MASK_LENGTH];
System.arraycopy(info, MEConstants.ADDER_LENGTH, mask, 0, MEConstants.MASK_LENGTH);
byte[] infoBytes = new byte[info.length - MEConstants.ADDER_PLUS_MASK_LENGTH];
System.arraycopy(info, MEConstants.ADDER_PLUS_MASK_LENGTH, infoBytes, 0, info.length - MEConstants.ADDER_PLUS_MASK_LENGTH);
return new String(byteArrayToCharArray(decode(adder, mask, infoBytes)));
}
/**
* Process the datastream from the Tier 0 device.
**/
public void run()
{
boolean jdbc = false;
try
{
boolean disconnected = false;
// If the client disconnects or is performing JdbcMe functions, then
// we exit this Thread and continue to listen for connections on the
// Server socket.
while (!disconnected & !jdbc)
{
// Process next datastream from client.
int cmd = input_.readInt();
if (Trace.isTraceOn())
Trace.log(Trace.PROXY, "ME Datastream Request: " + Integer.toHexString(cmd));
switch (cmd)
{
case MEConstants.SIGNON:
signon();
break;
case MEConstants.COMMAND_CALL:
doCommandCall();
break;
case MEConstants.PROGRAM_CALL:
doProgramCall();
break;
case MEConstants.DATA_QUEUE_READ:
doDataQueueRead();
break;
case MEConstants.DATA_QUEUE_WRITE:
doDataQueueWrite();
break;
case MEConstants.CONN_CLOSE:
case MEConstants.CONN_COMMIT:
case MEConstants.CONN_CREATE_STATEMENT:
case MEConstants.CONN_CREATE_STATEMENT2:
case MEConstants.CONN_NEW:
case MEConstants.CONN_PREPARE_STATEMENT:
case MEConstants.CONN_ROLLBACK:
case MEConstants.CONN_SET_AUTOCOMMIT:
case MEConstants.CONN_SET_TRANSACTION_ISOLATION:
case MEConstants.STMT_CLOSE:
case MEConstants.STMT_EXECUTE:
case MEConstants.STMT_GET_RESULT_SET:
case MEConstants.STMT_GET_UPDATE_COUNT:
case MEConstants.PREP_EXECUTE:
case MEConstants.RS_ABSOLUTE:
case MEConstants.RS_AFTER_LAST:
case MEConstants.RS_BEFORE_FIRST:
case MEConstants.RS_CLOSE:
case MEConstants.RS_DELETE_ROW:
case MEConstants.RS_FIRST:
case MEConstants.RS_INSERT_ROW:
case MEConstants.RS_IS_AFTER_LAST:
case MEConstants.RS_IS_BEFORE_FIRST:
case MEConstants.RS_IS_FIRST:
case MEConstants.RS_IS_LAST:
case MEConstants.RS_LAST:
case MEConstants.RS_NEXT:
case MEConstants.RS_PREVIOUS:
case MEConstants.RS_RELATIVE:
case MEConstants.RS_UPDATE_ROW:
if (service_ == null)
service_ = new JdbcMeService();
service_.setDataStreams(input_, output_);
try
{
if (Trace.isTraceOn())
Trace.log(Trace.PROXY, "Trying service com.ibm.as400.micro.JdbcMeService for Function id " + Integer.toHexString(cmd));
if (service_.acceptsRequest(cmd))
break; // maybe throw exception
if (Trace.isTraceOn())
Trace.log(Trace.PROXY, "Servicing function id " + Integer.toHexString(cmd));
// Request the function execution.
service_.handleRequest(cmd);
if (Trace.isTraceOn())
Trace.log(Trace.PROXY, "Service complete");
// Always flush the buffer at the end of the request.
output_.flush();
// TODO: A resource cleanup call for each service is needed here...
}
catch (EOFException eof)
{
if (Trace.isTraceOn())
Trace.log(Trace.ERROR, "EOF: Client disconnected", eof);
}
catch (IOException ioe)
{
if (Trace.isTraceOn())
Trace.log(Trace.ERROR, "IOException detected - finally block handles cleanup.", ioe);
}
break;
case MEConstants.DISCONNECT:
if (Trace.isTraceOn())
Trace.log(Trace.PROXY, "ToolboxME disconnect received.");
close();
disconnected = true;
break;
default:
output_.writeInt(MEConstants.EXCEPTION_OCCURRED);
output_.writeInt(MEConstants.REQUEST_NOT_SUPPORTED);
output_.writeUTF("ToolboxME Request not supported: " + Integer.toHexString(cmd) );
output_.flush();
}
}
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, e);
stop(e.getMessage());
}
close();
}
/**
* Receives the signon datastream from the client, connects to the i5/OS system, and sends a reply.
*
* Datastream request format:
* String - system name
* String - user profile
* String - password
*
* Datastream reply format:
* int - signon return code (e.g. failed, succeeded, etc.)
**/
private void signon() throws IOException
{
int clientDataStreamLevel = input_.readInt();
if (Trace.isTraceOn())
Trace.log(Trace.PROXY, "Micro client datastream level: " + clientDataStreamLevel);
byte[] proxySeed = new byte[MEConstants.ADDER_LENGTH];
input_.readBytes(proxySeed);
// Tell them what our datastream level is.
output_.writeInt(DATASTREAM_LEVEL);
// Generate, hold, and send them our seed.
byte[] remoteSeed = nextBytes(new Random(), MEConstants.MASK_LENGTH);
output_.writeBytes(remoteSeed);
output_.flush();
String serverName = input_.readUTF();
String userid = input_.readUTF();
int numBytes = input_.readInt(); // Get length of buffer to allocate.
byte[] bytes = new byte[numBytes];
input_.readBytes(bytes); // Note: This value is both twiddled and encoded.
// Disconnect if we were previously signed on.
// We only allow one AS400 object per thread/connection.
if (system_ != null)
system_.disconnectAllServices();
boolean retVal = false;
try
{
system_ = new com.ibm.as400.access.AS400(serverName, userid, resolve(decode(proxySeed, remoteSeed, bytes)));
// Wireless devices don't have NLS support in the KVM yet.
// So we only want to display english messages.
system_.setLocale(Locale.US);
retVal = system_.validateSignon();
}
catch (Exception e)
{
system_ = null;
output_.writeInt(MEConstants.EXCEPTION_OCCURRED);
output_.flush();
sendException(e);
return;
}
int retCode = MEConstants.SIGNON_SUCCEEDED;
if (!retVal)
{
system_ = null;
retCode = MEConstants.SIGNON_FAILED;
}
// We can differentiate more return codes here later on.
output_.writeInt(retCode);
output_.flush();
}
/**
* Stop the ME server.
**/
private final void stop(String failure)
{
try
{
output_.writeInt(MEConstants.EXCEPTION_OCCURRED);
output_.writeUTF(failure);
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error returning exception, during stop, to ME client.", e);
}
try
{
output_.flush();
}
catch (Exception e)
{
if (Trace.isTraceErrorOn ())
Trace.log (Trace.ERROR, "Error flushing output stream during MEServer stop.", e);
}
close();
}
/**
* Map a Toolbox exception constant to the corresponding MEException constant and
* send the text message of that exception to the client.
*
* @param e The exception.
**/
private void sendException(Exception e) throws IOException
{
if ( Trace.isTraceOn() )
Trace.log(Trace.PROXY, "ME Exception Occurred: \n", e);
// Determine what the exception was and then map it
// the the MEException return code and send it back
// to the Tier 0 client.
if (e instanceof AS400SecurityException)
{
int rc = ((AS400SecurityException)e).getReturnCode();
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC, "Exception return code: " + rc);
switch (rc)
{
case AS400SecurityException.PASSWORD_ERROR:
output_.writeInt(MEException.PASSWORD_ERROR);
output_.writeUTF( e.getMessage() );
break;
case AS400SecurityException.PASSWORD_EXPIRED:
output_.writeInt(MEException.PASSWORD_EXPIRED);
output_.writeUTF( e.getMessage() );
break;
case AS400SecurityException.PASSWORD_INCORRECT:
output_.writeInt(MEException.PASSWORD_INCORRECT);
output_.writeUTF( e.getMessage() );
break;
case AS400SecurityException.USERID_NOT_SET:
output_.writeInt(MEException.USERID_NOT_SET);
output_.writeUTF( e.getMessage() );
break;
case AS400SecurityException.USERID_DISABLE:
output_.writeInt(MEException.USERID_DISABLE);
output_.writeUTF( e.getMessage() );
break;
case AS400SecurityException.USERID_UNKNOWN:
output_.writeInt(MEException.USERID_UNKNOWN);
output_.writeUTF( e.getMessage() );
break;
default:
output_.writeInt(MEException.AS400_SECURITY_EXCEPTION);
output_.writeUTF( e.getMessage() );
break;
}
}
else if (e instanceof PcmlException)
{
output_.writeBoolean(false);
output_.writeInt(MEException.PCML_EXCEPTION);
output_.writeUTF( e.getLocalizedMessage() );
}
else if (e instanceof ObjectDoesNotExistException)
{
output_.writeInt(MEException.OBJECT_DOES_NOT_EXIST);
output_.writeUTF( e.getMessage() );
}
else if (e instanceof ExtendedIllegalArgumentException)
{
int rc = ((ExtendedIllegalArgumentException)e).getReturnCode();
if (rc == ExtendedIllegalArgumentException.LENGTH_NOT_VALID)
output_.writeInt(MEException.LENGTH_NOT_VALID);
else
output_.writeInt(MEException.PARAMETER_VALUE_NOT_VALID);
output_.writeUTF( e.getMessage() );
}
else if (e instanceof ExtendedIllegalStateException)
{
output_.writeInt(MEException.PROPERTY_NOT_SET);
output_.writeUTF( e.getMessage() );
}
else if (e instanceof ErrorCompletingRequestException)
{
int rc = ((ErrorCompletingRequestException)e).getReturnCode();
if (rc == ErrorCompletingRequestException.LENGTH_NOT_VALID)
output_.writeInt(MEException.LENGTH_NOT_VALID);
else
output_.writeInt(MEException.UNKNOWN);
output_.writeUTF( e.getMessage() );
}
else if (e instanceof ConnectionDroppedException)
{
int rc = ((ConnectionDroppedException)e).getReturnCode();
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC, "Exception return code: " + rc);
if (rc == ConnectionDroppedException.CONNECTION_DROPPED)
{
output_.writeInt(MEException.CONNECTION_DROPPED);
output_.writeUTF( e.getMessage() );
}
else
{
output_.writeInt(MEException.UNKNOWN);
output_.writeUTF( e.getMessage() );
}
}
else if (e instanceof IllegalObjectTypeException)
{
output_.writeInt(MEException.ILLEGAL_OBJECT_TYPE);
output_.writeUTF( e.getMessage() );
}
else if (e instanceof ServerStartupException)
{
int rc = ((ServerStartupException) e).getReturnCode();
if (Trace.isTraceOn())
Trace.log(Trace.DIAGNOSTIC, "Exception return code: " + rc);
if ( rc == ServerStartupException.SERVER_NOT_STARTED)
{
output_.writeInt(MEException.SERVER_NOT_STARTED);
output_.writeUTF( e.getMessage() );
}
else
{
output_.writeInt(MEException.UNKNOWN);
output_.writeUTF( e.getMessage() );
}
}
else if (e instanceof UnknownHostException)
{
output_.writeInt(MEException.UNKNOWN_HOST);
output_.writeUTF( e.toString() );
}
else if (e instanceof IOException)
{
output_.writeInt(MEException.UNKNOWN);
output_.writeUTF( e.toString() );
}
else
{
output_.writeInt(MEException.UNKNOWN);
output_.writeUTF( e.getMessage() );
}
output_.flush();
}
/**
* Prints the application usage information.
*
* @param out The print stream for usage information.
**/
static void usage (PrintStream out)
{
final String usage = ResourceBundleLoader_m.getText("ME_SERVER_USAGE");
final String optionslc = ResourceBundleLoader_m.getText("ME_SERVER_OPTIONSLC");
final String optionsuc = ResourceBundleLoader_m.getText("ME_SERVER_OPTIONSUC");
final String shortcuts = ResourceBundleLoader_m.getText("ME_SERVER_SHORTCUTS");
out.println (usage + ":");
out.println ();
out.println (" com.ibm.as400.access.MEServer [ " + optionslc + " ]");
out.println ();
out.println (optionsuc + ":");
out.println ();
out.println (" -pcml [pcml doc]");
out.println (" -port port");
out.println (" -verbose [true | false]");
out.println (" -help");
out.println ();
out.println (shortcuts + ":");
out.println (" -v [true | false]");
out.println (" -pc pcml doc1 [;pcml doc2;...]");
out.println (" -po port");
out.println (" -h");
out.println (" -?");
}
}