at.spardat.xma.rpc.RemoteCallClient Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* s IT Solutions AT Spardat GmbH - initial API and implementation
*******************************************************************************/
// @(#) $Id: RemoteCallClient.java 10268 2013-01-11 10:34:57Z dschwarz $
package at.spardat.xma.rpc;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;
import at.spardat.enterprise.exc.BaseException;
import at.spardat.enterprise.exc.SysException;
import at.spardat.xma.boot.component.IRtXMASessionClient;
import at.spardat.xma.boot.logger.LogLevel;
import at.spardat.xma.boot.transport.CommunicationException;
import at.spardat.xma.boot.transport.ConnectException;
import at.spardat.xma.boot.transport.Transport;
import at.spardat.xma.boot.transport.XMA_URI;
import at.spardat.xma.component.ComponentClient;
import at.spardat.xma.exception.Codes;
import at.spardat.xma.monitoring.client.TimingEventListClient;
import at.spardat.xma.page.PageClient;
import at.spardat.xma.security.XMAContext;
import at.spardat.xma.serializer.Deserializer;
import at.spardat.xma.serializer.Serializer;
import at.spardat.xma.serializer.SerializerFactory;
import at.spardat.xma.serializer.SerializerFactoryClient;
import at.spardat.xma.session.XMASessionClient;
import at.spardat.xma.util.ByteArray;
/**
* Used to construct a RemoteCall at the client side of XMA.
*
* @author YSD, 03.08.2003 00:18:55
*/
public class RemoteCallClient extends RemoteCall {
private final static boolean disableBusyShell;
static {
if ("true".equalsIgnoreCase(System.getProperty("disableBusyShellForRemoteCall"))) {
disableBusyShell = true;
} else {
disableBusyShell = false; // default value
}
}
/**
* The component that launched this RemoteCall.
*/
private ComponentClient issuer_;
/**
* The page that lauched this RemoteCall. Is null
* if the call is launched from a component.
*/
private PageClient issuerPage_;
/**
* Every execution adds a CallMeasurement to this list, the following
* RPC removes the first.
*/
private static LinkedList measurements_ = new LinkedList();
/**
* When an RPC is executed, its session is entered in this set in order
* to avoid to submitting nested requests; e.g., submitting a request
* while an old is active.
*/
private static HashSet fRpcActiveSessions = new HashSet();
/**
* communicate encrypted (use SSL)
*/
private boolean needEncryption = false;
/**
* Constructs a RemoteCall object
*
* @param issuer the client component that wants to execute this remote call.
* @param eventName the name of the event at the server side that should
* be executed.
* @exception IllegalArgumentException if any argument is null or
* eventName is empty.
*/
public RemoteCallClient (ComponentClient issuer, String eventName) {
if (issuer == null || eventName == null || eventName.length() == 0) {
throw new IllegalArgumentException();
}
issuer_ = issuer;
data_.eventName_ = eventName;
}
/**
* Constructs a RemoteCall object where the issuer is a page
*
* @param issuer the client page that wants to execute this remote call.
* @param eventName the name of the event at the server side that should
* be executed.
* @exception IllegalArgumentException if any argument is null or
* eventName is empty.
*/
public RemoteCallClient (PageClient issuer, String eventName) {
if (issuer == null || eventName == null || eventName.length() == 0) {
throw new IllegalArgumentException();
}
issuerPage_ = issuer;
issuer_ = issuer.getComponent();
data_.eventName_ = eventName;
}
/**
* Determines if data sent to the server of length should be compressed.
*/
private boolean doCompress (int length) {
String comprThrAsString = issuer_.getSession().getRuntimeProperty("RpcCompressionThreshold");
if (comprThrAsString == null) {
throw new SysException ("Undefined property 'RpcCompressionThreshold'. Your root.properties must include /at/spardat/xma/xmaruntime.properties.");
}
int compressionThreshold = Integer.parseInt (comprThrAsString);
if (compressionThreshold == -1) {
return false;
} else {
return length > compressionThreshold;
}
}
/**
* Executes this RemoteCall. The call is made to the server, the server side
* event method is executed.
*
* @return A RemoteReply object optionally holding parameters you have set
* at the server side.
* @throws BaseException all exceptions from the server side event method are
* packed into BaseExceptions and thrown again here. If the server side
* event method already throws an BaseException, is is not wrapped
* in an BaseException again.
*/
public RemoteReply execute () throws BaseException {
RemoteReply result = null;
XMASessionClient session = issuer_.getSession();
try {
/**
* check if this session has not already an active RPC running
*/
synchronized (fRpcActiveSessions) {
if (fRpcActiveSessions.contains (session)) {
throw new SysException ("Cannot call multiple RPCs within one session.").setCode(Codes.CLIENT_RECURSIVE_RPC);
}
fRpcActiveSessions.add (session);
}
/**
* execute the rpc
*/
result = execute0 ();
callGlobalEvents(result);
} finally {
/**
* remove the session of the component from the hashmap of active sessions
*/
synchronized (fRpcActiveSessions) {
fRpcActiveSessions.remove(session);
}
}
/**
* call rpcFinished on calling component or page
*/
if (issuerPage_ != null) {
issuerPage_.rpcFinished (this, result);
} else {
issuer_.rpcFinished (this, result);
}
return result;
}
/**
* retrieve GlobalEvents and iterate over GlobalEventListeners
* if GlobalEvents were transmitted.
*
* @param result
* @since version_number
* @author s3460
*/
private void callGlobalEvents(RemoteReply result) {
Collection globalEvents = (Collection) result.getParameter(RemoteReply.PARAM_GLOBAL_EVENTS);
if(globalEvents != null){
issuer_.getSession().callGlobalEventListener(globalEvents);
}
}
/**
* Implementation to execute a RemoteCall.
*/
private RemoteReply execute0 () throws BaseException {
/**
* acquire the data necessary to transmit to the server
*/
XMASessionClient session = issuer_.getSession();
data_.namComponent_ = issuer_.getName();
data_.idComponent_ = issuer_.getId();
data_.deadComponents_ = issuer_.getSession().getIdsOfDeadComponents();
data_.serverChangeNumber_ = issuer_.getSCN();
data_.issuerIsPage_ = (issuerPage_ != null);
if (issuerPage_ != null) {
data_.idPage_ = issuerPage_.getId();
data_.idPageType_ = issuerPage_.getTypeId();
}
data_.fullSync_ = issuer_.isStateless() || issuer_.isOutOfSyncWithServer();
// if there is a last call-measurement, transmit it
RemoteCallData.CallMeasurement lastMeasurement = getOldestMeasurement();
if (lastMeasurement != null){
lastMeasurement.setClientTimingEvents(TimingEventListClient.getEvents());
data_.setLastMeasurement(lastMeasurement);
}
/**
* copy properties to their models
*/
issuer_.props2model();
/**
* finally, the model deltas
*/
SerializerFactory fac = new SerializerFactoryClient();
Serializer ser = fac.createSerializer(session, 4096);
boolean binarySerialization = fac.isModeBinary(session);
try {
// compute all changes of the pages in issuers_ component
// externalize full if the component is stateless
issuer_.externalize (ser, data_.fullSync_);
data_.pageDeltas_ = ser.getResult().getBytes();
} catch (Exception ex) {
// this exception is considered a programming error
throw new SysException (ex, "cannot externalize page deltas").setCode(Codes.EXT_PAGES_CLIENT); //$NON-NLS-1$
}
/**
* serialize all the above information
*/
Serializer allSer = fac.createSerializer(session, 8192);
allSer.addHeader();
ByteArray allSerResult = null;
int sizeUncompressed = 0;
int sizeCompressed = 0;
try {
data_.externalize(allSer);
allSerResult = allSer.getResult();
} catch (Exception ex) {
throw new SysException (ex, "cannot produce RemoteCall data stream").setCode(Codes.EXT_STREAM_CLIENT);
}
/**
* compress
*/
sizeUncompressed = allSerResult.size();
if (doCompress(sizeUncompressed) && binarySerialization) {
allSerResult = allSerResult.getCompressed();
sizeCompressed = allSerResult.size();
}
if (!binarySerialization) {
allSerResult.setComputeHeaderLength(false);
}
/**
* At this point, serialization has been successful. Before actually doing the remote call,
* we output some statistics about the transfer sizes.
*/
XMAContext xmaContext = issuer_.getContext();
boolean doTrace = xmaContext.isLocal() || XMAContext.devel.equals(xmaContext.getEnvironment());
if (doTrace) {
int bytesDeltas = data_.getExternalizedPageDeltasSize();
int numParameters = data_.getParameterCount();
System.out.println ("RemoteCall: " + data_.eventName_ + " --------------------------------------------------------------");
String sentString = "sent: " + sizeUncompressed + " bytes";
if (sizeCompressed > 0) {
sentString = sentString + " uncompressed (" + sizeCompressed + " compressed)";
}
System.out.println ("RemoteCall: " + sentString
+", models: " + bytesDeltas
+" bytes, others: " + (sizeUncompressed-bytesDeltas) + " bytes. " +
(numParameters == 0 ? "" : "" + numParameters + " parameters."));
if (issuer_.isOutOfSyncWithServer()) {
System.out.println ("Waning: This is a full sync because client and server component are out of sync!!!!!");
}
}
/**
* Do the RPC using a Transport object
*/
byte [] transportOutputBytes = null;
long serverEventTime = System.currentTimeMillis();
boolean transportSuccess = false;
try {
transportOutputBytes = doTransport(session, allSerResult);
transportSuccess = true;
} catch (ConnectException x) {
// the server could not be reached
outOfSyncWith (new SysException (x, Codes.getText(Codes.SERVER_UNREACHABLE)).setCode(Codes.SERVER_UNREACHABLE)
.setShowToEndUser(true));
} catch (Exception ex) {
// at this point, we do not know what the server really did; we commit and set the component out of sync
outOfSyncWith (new SysException (ex, Codes.getText(Codes.SERVER_ERROR)).setCode(Codes.SERVER_ERROR)
.setShowToEndUser(true));
} finally {
serverEventTime = System.currentTimeMillis() - serverEventTime; // records, how long the event took
// record a CallMeasurement-object
RemoteCallData.CallMeasurement cm = new RemoteCallData.CallMeasurement();
cm.durationMSecs_ = (int)serverEventTime;
cm.success_ = transportSuccess;
if("true".equalsIgnoreCase(session.getRuntimeProperty("rpcMessureSize","false"))) {
cm.client2serverSize = sizeCompressed!=0 ? sizeCompressed : sizeUncompressed;
cm.server2clientSize = transportOutputBytes!=null ? transportOutputBytes.length : 0;
}
addMeasurement(cm);
}
/**
* at this point, the client models are committed; whatever kind of error may happen
* afterwards, we set the component out of sync
*/
issuer_.commit();
RemoteReply reply = new RemoteReply();
RemoteReplyData replyData = reply.getReplyData();
int replySizeUncompressed = 0;
int replySizeCompressed = 0;
try {
/**
* First 4 bytes in resultData is the length. This must match with
* the length of the array.
*/
if (transportOutputBytes == null || transportOutputBytes.length < 4) {
throw new SysException ("server returned less than 4 bytes RemoteReply").setCode(Codes.CLIENT_READ_LENGTH);
}
ByteArray transportOutputByteArray = new ByteArray (transportOutputBytes);
transportOutputByteArray.setHeader(true);
int expectedLength = transportOutputByteArray.getLengthInHeader();
if (expectedLength != -1 && expectedLength != transportOutputBytes.length) {
throw new SysException ("expected result of "+expectedLength+", but got "+transportOutputBytes.length)
.setCode(Codes.LENGTH_MISMATCH_AT_CLIENT);
}
/**
* uncompressed if compressed
*/
if (transportOutputByteArray.isCompressed()) {
replySizeCompressed = transportOutputBytes.length;
transportOutputByteArray = transportOutputByteArray.getUncompressed();
replySizeUncompressed = transportOutputByteArray.size();
} else {
replySizeUncompressed = transportOutputBytes.length;
}
/**
* Deserialize ReplyData from byte array
*/
try {
Deserializer allDeser = fac.createDeserializer(session, transportOutputByteArray.getBuffer(), ByteArray.HEADER_LEN, transportOutputByteArray.size()-ByteArray.HEADER_LEN);
replyData.internalize (allDeser);
} catch (Exception ex) {
throw new SysException (ex, "client cannot internalize replyData").setCode(Codes.CLIENT_INTERNALIZE_RESPONSE);
}
/**
* Output statistics about reply
*/
if (doTrace) {
int bytesDeltas = replyData.getExternalizedPageDeltasSize();
int numParameters = replyData.getParameterCount();
String rcvString = "received: " + replySizeUncompressed + " bytes";
if (replySizeCompressed > 0) {
rcvString = rcvString + " uncompressed (" + replySizeCompressed + " compressed)";
}
System.out.println ("RemoteCall: " + rcvString
+", models: " + bytesDeltas
+" bytes, others: " + (replySizeUncompressed-bytesDeltas) + " bytes" +
(numParameters == 0 ? "" : ", " + numParameters + " parameters") +
", time: " + serverEventTime + " msecs.");
}
/**
* Internalize changes from server
*/
try {
if (replyData.pageDeltas_ != null) {
Deserializer modelDeser = fac.createDeserializer(session, replyData.pageDeltas_);
issuer_.internalize (modelDeser, null);
issuer_.commit();
}
} catch (Exception ex) {
throw new SysException (ex, "client cannot internalize deltas from server").setCode(Codes.CLIENT_INTERNALIZE_DELTAS);
}
} catch (Exception ex) {
// whatever happens here, the component is out of sync
BaseException result = null;
if (ex instanceof BaseException) {
result = (BaseException)ex;
} else {
result = new SysException (ex, "client cannot process server response").setCode(Codes.CLIENT_PROCESS_SERVER_RESPONSE);
}
outOfSyncWith (result);
}
if (replyData.exception_ != null && replyData.exception_.getCode() == Codes.SERVER_INTERNALIZE_DELTAS) {
outOfSyncWith(replyData.exception_);
}
/**
* what follows are actions that must be taken if a server side event has been called sucessfully
* (at least from the perspective of the XMA runtime).
*/
issuer_.setSCN(replyData.serverChangeNumber_);
issuer_.setOutOfSyncWithServer(false);
// mark all pages of the component as not new
Iterator iter = issuer_.getPageModels();
while (iter.hasNext()) {
PageClient p = (PageClient) iter.next();
p.setNew (false);
}
// reset list of dead components
issuer_.getSession().resetDeadComponentList();
/**
* set properties in the component that might have changed at the server;
* this is done deliberately here at the end of the RCP, because all other
* models need to be available at this point because changing properties
* may trigger change listeners the programmer could have registered.
*/
try {
issuer_.model2props();
} catch (Exception x) {
if (replyData.exception_ == null) {
throw new SysException (x);
} else {
issuer_.getSession().getLogger().log(LogLevel.WARNING, "problem in setting properties in RPC:", x);
}
}
/**
* if the ReplyData contained an Exception from the server, throw it
*/
if (replyData.exception_ != null) {
throw replyData.exception_;
}
return reply;
}
/**
* This method actually calls transport.callServerEvent(), which does the communictaion.
* Handles encryption of the communiction if needEncrytion is true.
* @param session to use
* @param allSerResult data to send
* @return the received data
* @throws CommunicationException
*/
private byte[] doTransport(XMASessionClient session,ByteArray allSerResult) throws CommunicationException {
XMA_URI uri = session.getUri();
uri.setResource("rpc");
if(needEncryption&&!"https".equals(uri.getProtocol_())) {
uri.setProtocol("https");
String ports = session.getRuntimeProperty("SSLPort","443");
for(StringTokenizer tok=new StringTokenizer(ports,",;|");tok.hasMoreTokens();) {
String port = tok.nextToken().trim();
uri.setPort(new Integer(port).intValue());
try {
return callServerEvent(session, uri, allSerResult.getBytes());
} catch (ConnectException ex) {
if(tok.hasMoreTokens()) {
// exception allready logged by Transport
session.getLogger().log(LogLevel.INFO,"Server not reachable at port "+port+" trying next secure port");
} else {
throw ex;
}
}
}
// not reachable, but eclipse don't know this
throw new RuntimeException("unreachable code reached");
} else {
return callServerEvent(session, uri, allSerResult.getBytes());
}
}
public byte[] callServerEvent(final IRtXMASessionClient session, final XMA_URI eventHandler, final byte[] input) throws CommunicationException {
/* Show the busy shell only in case it is not explicitly disabled and when there
* are on SWT messages which are waiting to be processed by the UI thread. */
boolean isUIThread;
isUIThread = BusyExecutorHelper.getDisplay().getThread() == Thread.currentThread();
if (isUIThread && disableBusyShell == false && !BusyExecutorHelper.hasDisplayMessages() ) {
BusyExecutor executor = new BusyExecutor() {
public Object execute() throws Exception {
final Transport transport = Transport.getTransport();
return transport.callServerEvent(session, eventHandler, input);
}
};
executor.startExecutor();
if (executor.getException() != null) {
if (executor.getException() instanceof CommunicationException) {
throw (CommunicationException) executor.getException();
} else
if (executor.getException() instanceof RuntimeException) {
throw (RuntimeException) executor.getException();
} else {
throw new RuntimeException( executor.getException() );
}
}
return (byte[]) executor.getResult();
} else {
final Transport transport = Transport.getTransport();
return transport.callServerEvent(session, eventHandler, input);
}
}
/**
* Helper method for abnormal terminations in execute.
*/
private void outOfSyncWith (BaseException ex) {
issuer_.setOutOfSyncWithServer(true);
issuer_.commit();
throw ex;
}
/**
* Returns the oldest available CallMeasurement and removes it from the list
* of measurements.
*
* @return null if the list is empty, otherwise the oldest CallMeasurement.
*/
private RemoteCallData.CallMeasurement getOldestMeasurement () {
synchronized (measurements_) {
if (measurements_.size() == 0) {
return null;
}
return (RemoteCallData.CallMeasurement) measurements_.removeFirst();
}
}
/**
* Adds a CallMeasurement to the end of the list of measurements.
*/
private void addMeasurement (RemoteCallData.CallMeasurement cm) {
synchronized (measurements_) {
measurements_.addLast (cm);
}
}
/**
* Determines if data has to be send encrypted.
* @param needEncryption if true https will be used for transport.
* @author s2877
*/
public void setNeedEncryption(boolean needEncryption) {
this.needEncryption = needEncryption;
}
}