All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.spardat.xma.rpc.RemoteCallClient Maven / Gradle / Ivy

/*******************************************************************************
 * 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 7841 2011-05-20 15:04:27Z aku $
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);
                    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);
        }

        /**
         * 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;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy