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

dorkbox.network.rmi.RemoteObjectInvocationHandler Maven / Gradle / Ivy

/*
 * Copyright 2010 dorkbox, llc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (c) 2008, Nathan Sweet
 * 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 Esoteric Software 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER OR 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 dorkbox.network.rmi;


import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.util.RMISerializationManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Handles network communication when methods are invoked on a proxy.
 */
class RemoteObjectInvocationHandler implements InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(RemoteObjectInvocationHandler.class);

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition responseCondition = this.lock.newCondition();

    private final InvokeMethodResult[] responseTable = new InvokeMethodResult[64];
    private final boolean[] pendingResponses = new boolean[64];

    private final Connection connection;
    public final int objectID;
    private final String proxyString;
    private final RemoteInvocationResponse responseListener;

    private int timeoutMillis = 3000;
    private boolean isAsync = false;

    private boolean transmitReturnValue = true;
    private boolean transmitExceptions = true;

    private boolean enableToString;

    private boolean udp;
    private boolean udt;

    private Byte lastResponseID;
    private byte nextResponseId = (byte) 1;

    RemoteObjectInvocationHandler(final Connection connection, final int objectID) {
        super();
        this.connection = connection;
        this.objectID = objectID;
        this.proxyString = "";

        this.responseListener = new RemoteInvocationResponse() {
            @Override
            public
            void disconnected(Connection connection) {
                close();
            }

            @Override
            public
            void received(Connection connection, InvokeMethodResult invokeMethodResult) {
                byte responseID = invokeMethodResult.responseID;

                if (invokeMethodResult.objectID != objectID) {
//				    System.err.println("FAILED: " + responseID);
//				    logger.trace("{} FAILED to received data: {}  with id ({})", connection, invokeMethodResult.result, invokeMethodResult.responseID);
                    return;
                }

//				logger.trace("{} received data: {}  with id ({})", connection, invokeMethodResult.result, invokeMethodResult.responseID);
                synchronized (this) {
                    if (RemoteObjectInvocationHandler.this.pendingResponses[responseID]) {
                        RemoteObjectInvocationHandler.this.responseTable[responseID] = invokeMethodResult;
                    }
                }

                RemoteObjectInvocationHandler.this.lock.lock();
                try {
                    RemoteObjectInvocationHandler.this.responseCondition.signalAll();
                } finally {
                    RemoteObjectInvocationHandler.this.lock.unlock();
                }
            }
        };

        connection.listeners()
                  .add(this.responseListener);
    }



    @SuppressWarnings({"AutoUnboxing", "AutoBoxing", "NumericCastThatLosesPrecision", "IfCanBeSwitch"})
    @Override
    public
    Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
        final Class declaringClass = method.getDeclaringClass();
        if (declaringClass == RemoteObject.class) {
            // manage all of the RemoteObject proxy methods

            String name = method.getName();
            if (name.equals("close")) {
                close();
                return null;
            }
            else if (name.equals("setResponseTimeout")) {
                this.timeoutMillis = (Integer) args[0];
                return null;
            }
            else if (name.equals("setAsync")) {
                this.isAsync = (Boolean) args[0];
                return null;
            }
            else if (name.equals("setTransmitReturnValue")) {
                this.transmitReturnValue = (Boolean) args[0];
                return null;
            }
            else if (name.equals("setTransmitExceptions")) {
                this.transmitExceptions = (Boolean) args[0];
                return null;
            }
            else if (name.equals("setTCP")) {
                this.udp = false;
                this.udt = false;
                return null;
            }
            else if (name.equals("setUDP")) {
                this.udp = true;
                this.udt = false;
                return null;
            }
            else if (name.equals("setUDT")) {
                this.udp = false;
                this.udt = true;
                return null;
            }
            else if (name.equals("enableToString")) {
                this.enableToString = (Boolean) args[0];
                return null;
            }
            else if (name.equals("waitForLastResponse")) {
                if (this.lastResponseID == null) {
                    throw new IllegalStateException("There is no last response to wait for.");
                }
                return waitForResponse(this.lastResponseID);
            }
            else if (name.equals("getLastResponseID")) {
                if (this.lastResponseID == null) {
                    throw new IllegalStateException("There is no last response ID.");
                }
                return this.lastResponseID;
            }
            else if (name.equals("waitForResponse")) {
                if (!this.transmitReturnValue && !this.transmitExceptions && this.isAsync) {
                    throw new IllegalStateException("This RemoteObject is currently set to ignore all responses.");
                }
                return waitForResponse((Byte) args[0]);
            }

            // Should never happen, for debugging purposes only!
            throw new Exception("Invocation handler could not find RemoteObject method for " + name);
        }
        else if (!this.enableToString && declaringClass == Object.class && method.getName()
                                                                                 .equals("toString")) {
            return proxyString;
        }

        final Logger logger1 = RemoteObjectInvocationHandler.logger;

        EndPoint endPoint = this.connection.getEndPoint();
        final RMISerializationManager serializationManager = endPoint.getSerialization();

        InvokeMethod invokeMethod = new InvokeMethod();
        invokeMethod.objectID = this.objectID;
        invokeMethod.args = args;


        // which method do we access?
        CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass());

        for (int i = 0, n = cachedMethods.length; i < n; i++) {
            CachedMethod cachedMethod = cachedMethods[i];
            Method checkMethod = cachedMethod.origMethod;
            if (checkMethod == null) {
                checkMethod = cachedMethod.method;
            }

            // In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
            // This is to support calling RMI methods from an interface (that does pass the connection reference) to
            // an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
            // the interface, and the implType may override the method, so that we add the connection as the first in
            // the list of parameters.
            //
            // for example:
            // Interface: foo(String x)
            //      Impl: foo(Connection c, String x)
            //
            // The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
            // This MUST hold valid for both remote and local connection types.

            // To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
            // interface as the first parameter, and  .registerRemote(ifaceClass, implClass)  must be called.

            if (checkMethod.equals(method)) {
                invokeMethod.cachedMethod = cachedMethod;
                break;
            }
        }

        if (invokeMethod.cachedMethod == null) {
            String msg = "Method not found: " + method;
            logger1.error(msg);
            return msg;
        }


        byte responseID = (byte) 0;
        // An invocation doesn't need a response is if it's async and no return values or exceptions are wanted back.
        boolean ignoreResponse = this.isAsync && !(this.transmitReturnValue || this.transmitExceptions);
        if (ignoreResponse) {
            invokeMethod.responseData = (byte) 0; // 0 means do not respond.
        }
        else {
            synchronized (this) {
                // Increment the response counter and put it into the low bits of the responseID.
                responseID = this.nextResponseId++;
                if (this.nextResponseId > RmiBridge.responseIdMask) {
                    this.nextResponseId = (byte) 1;
                }
                this.pendingResponses[responseID] = true;
            }
            // Pack other data into the high bits.
            byte responseData = responseID;
            if (this.transmitReturnValue) {
                responseData |= (byte) RmiBridge.returnValueMask;
            }
            if (this.transmitExceptions) {
                responseData |= (byte) RmiBridge.returnExceptionMask;
            }
            invokeMethod.responseData = responseData;
        }

        // Sends our invokeMethod to the remote connection, which the RmiBridge listens for
        if (this.udp) {
            this.connection.send()
                           .UDP(invokeMethod)
                           .flush();
        }
        else if (this.udt) {
            this.connection.send()
                           .UDT(invokeMethod)
                           .flush();
        }
        else {
            this.connection.send()
                           .TCP(invokeMethod)
                           .flush();
        }

        if (logger1.isTraceEnabled()) {
            String argString = "";
            if (args != null) {
                argString = Arrays.deepToString(args);
                argString = argString.substring(1, argString.length() - 1);
            }
            logger1.trace(this.connection + " sent: " + method.getDeclaringClass()
                                                              .getSimpleName() +
                          "#" + method.getName() + "(" + argString + ")");
        }

        this.lastResponseID = (byte) (invokeMethod.responseData & RmiBridge.responseIdMask);


        if (this.isAsync) {
            Class returnType = method.getReturnType();
            if (returnType.isPrimitive()) {
                if (returnType == int.class) {
                    return 0;
                }
                if (returnType == boolean.class) {
                    return Boolean.FALSE;
                }
                if (returnType == float.class) {
                    return 0.0f;
                }
                if (returnType == char.class) {
                    return (char) 0;
                }
                if (returnType == long.class) {
                    return 0L;
                }
                if (returnType == short.class) {
                    return (short) 0;
                }
                if (returnType == byte.class) {
                    return (byte) 0;
                }
                if (returnType == double.class) {
                    return 0.0d;
                }
            }
            return null;
        }

        try {
            Object result = waitForResponse(this.lastResponseID);
            if (result != null && result instanceof Exception) {
                throw (Exception) result;
            }
            else {
                return result;
            }
        } catch (TimeoutException ex) {
            throw new TimeoutException("Response timed out: " + method.getDeclaringClass()
                                                                      .getName() + "." + method.getName());
        } finally {
            synchronized (this) {
                this.pendingResponses[responseID] = false;
                this.responseTable[responseID] = null;
            }
        }
    }

    /**
     * A timeout of 0 means that we want to disable waiting, otherwise - it waits in milliseconds
     */
    private
    Object waitForResponse(final byte responseID) throws IOException {
        long endTime = System.currentTimeMillis() + this.timeoutMillis;
        long remaining = this.timeoutMillis;

        if (remaining == 0) {
            // just wait however log it takes.
            InvokeMethodResult invokeMethodResult;
            synchronized (this) {
                invokeMethodResult = this.responseTable[responseID];
            }

            if (invokeMethodResult != null) {
                this.lastResponseID = null;
                return invokeMethodResult.result;
            }
            else {
                this.lock.lock();
                try {
                    this.responseCondition.await();
                } catch (InterruptedException e) {
                    Thread.currentThread()
                          .interrupt();
                    throw new IOException("Response timed out.", e);
                } finally {
                    this.lock.unlock();
                }
            }

            synchronized (this) {
                invokeMethodResult = this.responseTable[responseID];
            }
            if (invokeMethodResult != null) {
                this.lastResponseID = null;
                return invokeMethodResult.result;
            }
        }
        else {
            // wait for the specified time
            while (remaining > 0) {
                InvokeMethodResult invokeMethodResult;
                synchronized (this) {
                    invokeMethodResult = this.responseTable[responseID];
                }

                if (invokeMethodResult != null) {
                    this.lastResponseID = null;
                    return invokeMethodResult.result;
                }
                else {
                    this.lock.lock();
                    try {
                        this.responseCondition.await(remaining, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        Thread.currentThread()
                              .interrupt();
                        throw new IOException("Response timed out.", e);
                    } finally {
                        this.lock.unlock();
                    }
                }

                remaining = endTime - System.currentTimeMillis();
            }
        }

        // only get here if we timeout
        throw new TimeoutException("Response timed out.");
    }

    private
    void close() {
        this.connection.listeners()
                       .remove(this.responseListener);
    }

    @Override
    public
    int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + this.objectID;
        return result;
    }

    @Override
    public
    boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        RemoteObjectInvocationHandler other = (RemoteObjectInvocationHandler) obj;
        return this.objectID == other.objectID;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy