org.rzo.netty.ahessian.rpc.client.HessianProxyFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.rzo.netty.ahessian.rpc.client.HessianProxyFactory.java

Source

/*******************************************************************************
 * Copyright  2015 rzorzorzo@users.sf.net
 *
 * 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.
 *******************************************************************************/
package org.rzo.netty.ahessian.rpc.client;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;

import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.rzo.netty.ahessian.Constants;
import org.rzo.netty.ahessian.rpc.io.Hessian2Input;
import org.rzo.netty.ahessian.rpc.io.Hessian2Output;
import org.rzo.netty.ahessian.rpc.message.HessianRPCCallMessage;
import org.rzo.netty.ahessian.rpc.message.HessianRPCReplyMessage;
import org.rzo.netty.ahessian.rpc.stream.ClientStreamManager;
import org.rzo.netty.ahessian.rpc.stream.InputStreamReplyMessage;
import org.rzo.netty.ahessian.session.ClientSessionFilter;
import org.rzo.netty.ahessian.utils.MyBlockingQueue;
import org.rzo.netty.ahessian.utils.MyLinkedBlockingQueue;
import org.rzo.netty.ahessian.utils.MyReentrantLock;
import org.rzo.netty.ahessian.utils.TimedBlockingPriorityQueue;

import com.caucho.hessian4.io.AbstractHessianInput;
import com.caucho.hessian4.io.AbstractHessianOutput;
import com.caucho.hessian4.io.HessianRemoteObject;

/**
 * Handles client side hessian rpc proxy invocations and is a factory for
 * service proxies. <br>
 * A typical setup for a protocol in a TCP/IP socket would be: <br>
 * 
 * <pre>
 * Executor executor = ...
 * HessianProxyFactory proxyFactory = ...
 * 
 * {@link ChannelPipeline} pipeline = ...;
 * pipeline.addLast(&quot;inputStream&quot;, new InputStreamDecoder(_executor));
 * pipeline.addLast(&quot;outputStream&quot;, new OutputStreamEncoder());        
 * pipeline.addLast(&quot;hessianReplyDecoder&quot;, new HessianRPCReplyDecoder(_factory));
 * pipeline.addLast(&quot;hessianCallEncoder&quot;, new HessianRPCCallEncoder());
 * pipeline.addLast(&quot;hessianHandler&quot;, proxyFactory);
 * </pre>
 * 
 * <br>
 * Typical usage within the client would be:
 * 
 * <pre>
 * 
 * ClientBootstrap bootstrap = ...
 * ChannelPipelineFactory pipelinetFactory = new ...(proxyFactory)
 * bootstrap.setPipelineFactory(...)
 * bootstrap.connect(...)
 * 
 * // get a service proxy 
 * Map options = new HashMap();
 * options.put(&quot;id&quot;, &quot;myServiceName&quot;);
 * // AsynchMyServiceInterface is an interface including the same methods as MyServiceInterface 
 * //except that the return type is always of type HessianProxyFuture
 * AsynchMyServiceInterface service = (AsynchMyServiceInterface) factory.create(AsynchMyServiceInterface.class, getClassLoader(), options);
 * 
 * // invoke a service method
 * HessianProxyFuture future = service.myMethod();
 * // wait for the result
 * // if an exception is thrown by the server the exception is thrown by the call to the get() method 
 * Object result = future.get();
 * </pre>
 */
@Sharable
public class HessianProxyFactory extends ChannelInboundHandlerAdapter implements Constants {
    private static final int MAX_OPEN_CALLS = 50000;
    private volatile Map<Long, Future<Object>> _openCalls = Collections
            .synchronizedMap(new HashMap<Long, Future<Object>>());
    private volatile int _id = 0;
    private volatile Channel _channel = null;
    private volatile com.caucho.hessian4.client.HessianProxyFactory _factory = null;
    private volatile MyBlockingQueue<HessianRPCCallMessage> _pendingCalls;

    /** The _done listener. */
    Runnable _doneListener;

    /** The _executor. */
    Executor _executor;
    private Lock _lock = new MyReentrantLock();
    private Condition _connected = _lock.newCondition();

    /** The _stop. */
    boolean _stop = false;
    private String _name;
    private boolean _sessionListenerAdded = false;
    volatile private Runnable _closedSessionListener;
    volatile private Runnable _newSessionListener;
    volatile private Runnable _disconnectedListener;
    volatile private Runnable _connectedListener;

    Map<Object, InvocationHandler> _proxies = Collections.synchronizedMap(new HashMap<Object, InvocationHandler>());

    Timer _timer = new HashedWheelTimer();

    private volatile boolean _blocked = false;

    ClientStreamManager _clientStreamManager;

    /**
     * Instantiates a new hessian proxy factory.
     * 
     * @param executor
     *            the executor
     */
    public HessianProxyFactory(Executor executor, String name) {
        this(executor, name, null, new HashMap());
    }

    public HessianProxyFactory(Executor executor, String name, Map options) {
        this(executor, name, null, options);
    }

    public HessianProxyFactory(Executor executor, String name, ClassLoader loader, Map options) {
        _executor = executor;
        _name = name;
        if (options != null)
            _pendingCalls = new TimedBlockingPriorityQueue<HessianRPCCallMessage>(options, null,
                    "HessianProxyFactory-PendingCalls");
        else
            _pendingCalls = new MyLinkedBlockingQueue();
        if (loader == null)
            _factory = new com.caucho.hessian4.client.HessianProxyFactory();
        else
            _factory = new com.caucho.hessian4.client.HessianProxyFactory(loader);
        /*
         * _executor.execute(new Runnable() { public void run() {
         * Thread.currentThread().setName("HessianProxyFactory-Call-Tx");
         * HessianRPCCallMessage message = null; while (!_stop) { // if previous
         * message sent if (message == null) try { message =
         * _pendingCalls.take(); } catch (InterruptedException e1) {
         * Constants.ahessianLogger.warn("", e1); } if (message == null)
         * continue; _lock.lock(); Channel channel = getChannel(); while
         * (channel == null || !channel.isConnected() && !_stop) try {
         * _connected.await(1000, TimeUnit.MILLISECONDS); channel =
         * getChannel(); } catch (InterruptedException e) {
         * Constants.ahessianLogger.warn("", e); } _lock.unlock(); if (!_stop &&
         * message != null && message.getMethod() != null) try { ChannelFuture
         * future = channel.write(message); future.await(); if
         * (future.isSuccess()) { // if (_pendingCalls.size() == 0) //
         * channel.write(new Integer(0)); message = null; } else
         * ahessianLogger.warn("cannot send message, will retry"); } catch
         * (Exception ex) { Constants.ahessianLogger.warn("", ex); } else if
         * (message.getMethod() == null) message = null;
         * 
         * }
         * 
         * } });
         */
    }

    /**
     * Gets the hessian2 input.
     * 
     * @param is
     *            the is
     * 
     * @return the hessian2 input
     */
    public AbstractHessianInput getHessian2Input(InputStream is) {
        return new Hessian2Input(is);
    }

    public AbstractHessianOutput getHessian2Output(OutputStream out) {
        Hessian2Output out2 = new Hessian2Output(out);
        out2.setSerializerFactory(_factory.getSerializerFactory());
        return out2;
    }

    /**
     * Checks if is overload enabled.
     * 
     * @return true, if is overload enabled
     */
    public boolean isOverloadEnabled() {
        return _factory.isOverloadEnabled();
    }

    /**
     * Send request.
     * 
     * @param methodName
     *            the method name
     * @param args
     *            the args
     * @param options
     *            the options
     * 
     * @return the future< object>
     * 
     * @throws InterruptedException
     *             the interrupted exception
     */
    synchronized Future<Object> sendRequest(String methodName, Object[] args, Map options)
            throws InterruptedException {
        long t = System.currentTimeMillis();
        if (_blocked)
            throw new RuntimeException("send blocked");
        if (_stop)
            return null;
        Map<Object, Object> headers = options;
        final Long id = new Long(_id);
        _id++;
        headers.put(CALL_ID_HEADER_KEY, id);
        final HessianProxyFuture future = new HessianProxyFuture();
        future.handleCallbacks(args);
        final HessianRPCCallMessage message = new HessianRPCCallMessage(methodName, args, headers, null);
        int i = 0;
        while (_openCalls.size() > MAX_OPEN_CALLS && getChannel() != null) {
            // System.out.println("too many open calls -> wait "+i++);
            Thread.sleep(10);
        }
        _openCalls.put(id, future);
        Integer g = (Integer) options.get("group");
        final Integer group = g == null ? 0 : g;
        long timeout = _pendingCalls.getTimeout(group);
        if (timeout > 0) {
            TimerTask task = new TimerTask() {

                public void run(Timeout arg0) throws Exception {
                    _openCalls.remove(id);
                    future.timedOut();
                }

            };
            future.setTimeout(_timer.newTimeout(task, timeout, TimeUnit.MILLISECONDS));
        }
        Channel channel = null;
        /*
         * while ((channel = getChannel()) == null) { _lock.lock(); try {
         * _connected.await(100, TimeUnit.MILLISECONDS); } finally {
         * _lock.unlock(); } }
         */
        channel = getChannel();
        if (channel == null)
            throw new RuntimeException("channel closed");
        while (!channel.isWritable() && channel.isActive()) {
            // System.out.println("channel wait call "+Thread.currentThread().getName());
            Thread.sleep(100);
        }
        channel.write(message);
        // System.out.println("sendRequest "+(System.currentTimeMillis()-t));

        return future;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.jboss.netty.channel.SimpleChannelHandler#messageReceived(org.jboss
     * .netty.channel.ChannelHandlerContext,
     * org.jboss.netty.channel.MessageEvent)
     */
    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object e) throws Exception {
        if (e instanceof HessianRPCReplyMessage) {
            final HessianRPCReplyMessage message = (HessianRPCReplyMessage) e;
            {
                final Long id = message.getCallId();
                if (id != null) {
                    final HessianProxyFuture future = (HessianProxyFuture) _openCalls.get(id);
                    if (future == null) {
                        ahessianLogger.warn("no future found for call-id " + id);
                        return;
                    }
                    if (message.getCompleted() == null || Boolean.TRUE.equals(message.getCompleted()))
                        if ((!future.hasCallbacks())) {
                            _openCalls.remove(id);
                        }
                    if (_doneListener != null && _openCalls.isEmpty())
                        _doneListener.run();
                    if (future != null)
                        // setting message in future may fire listeners -> run
                        // in separate thread
                        _executor.execute(new Runnable() {
                            public void run() {
                                // System.out.println("executing callback");
                                try {
                                    if (message.getValue() instanceof InputStreamReplyMessage) {
                                        InputStream stream = _clientStreamManager.newInputStream(
                                                ((InputStreamReplyMessage) message.getValue()).getId());
                                        // caller should get a stream, not the
                                        // reply
                                        message.setValue(stream);
                                    }
                                    future.set(message, ctx);
                                    // check in case this was a callback
                                    if (future.isDone())
                                        if (!future.hasCallbacks()) {
                                            _openCalls.remove(id);

                                        }
                                } catch (Throwable e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    else
                        ahessianLogger.warn("no future for call reply " + id + " " + message.getValue());
                } else
                    ahessianLogger.warn("message missing id " + message);

            }
        } else if (e instanceof InputStreamReplyMessage) {
            _clientStreamManager.messageReceived((InputStreamReplyMessage) e);
        }
        // ctx.fireChannelRead(e);
        ctx.fireChannelReadComplete();
    }

    /**
     * Creates a service proxy.
     * 
     * @param api
     *            the "asynched" api of the service
     * @param loader
     *            the class loader for creating the proxy
     * @param options
     *            the options
     * 
     * @return the object
     */
    public Object create(Class api, ClassLoader loader, Map options) {
        if (api == null)
            throw new NullPointerException("api must not be null for HessianProxyFactory.create()");
        InvocationHandler handler = null;
        if (options == null)
            options = new HashMap();
        handler = new AsyncHessianProxy(this, api, options);
        if (options.get("sync") != null)
            handler = new SyncHessianProxy(handler);

        Object result = Proxy.newProxyInstance(loader, new Class[] { api, HessianRemoteObject.class }, handler);
        _proxies.put(result, handler);
        return result;

    }

    public void returnProxy(Object proxy) {
        Object handler = _proxies.remove(proxy);
        if (handler != null)
            if (handler instanceof SyncHessianProxy)
                handler = ((SyncHessianProxy) handler)._handler;
        ((AsyncHessianProxy) handler).invalidate();
    }

    /**
     * Gets the channel.
     * 
     * @return the channel
     */
    public Channel getChannel() {
        if (_channel == null || !_channel.isActive() || !_channel.isWritable())
            return null;
        return _channel;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.jboss.netty.channel.SimpleChannelHandler#channelConnected(org.jboss
     * .netty.channel.ChannelHandlerContext,
     * org.jboss.netty.channel.ChannelStateEvent)
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        _channel = ctx.channel();
        if (_connectedListener != null)
            _executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        _connectedListener.run();
                    } catch (Throwable ex) {
                        Constants.ahessianLogger.warn("", ex);
                    }
                }
            });
        _lock.lock();
        try {
            if (!_sessionListenerAdded) {
                if (ctx.pipeline().get(ClientSessionFilter.class) != null) {
                    ClientSessionFilter sessionHandler = (ClientSessionFilter) ctx.pipeline()
                            .get(ClientSessionFilter.class);
                    sessionHandler.addSessionClosedListener(new Runnable() {
                        public void run() {
                            _lock.lock();
                            try {
                                invalidateProxies();
                                _openCalls.clear();

                                _pendingCalls.clear();
                            } finally {
                                _lock.unlock();
                            }
                            if (_closedSessionListener != null)
                                try {
                                    _closedSessionListener.run();
                                } catch (Throwable ex) {
                                    Constants.ahessianLogger.warn("", ex);
                                }
                        }
                    });
                    sessionHandler.addSessionNewListener(new Runnable() {
                        public void run() {
                            if (_newSessionListener != null)
                                try {
                                    _newSessionListener.run();
                                } catch (Throwable ex) {
                                    Constants.ahessianLogger.warn("", ex);
                                }
                        }
                    });
                    _sessionListenerAdded = true;
                }
            }
        } finally {
            _connected.signal();
            _lock.unlock();
            super.channelActive(ctx);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.jboss.netty.channel.SimpleChannelHandler#channelDisconnected(org.
     * jboss.netty.channel.ChannelHandlerContext,
     * org.jboss.netty.channel.ChannelStateEvent)
     */
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        _channel = null;
        // _stop = true;
        _lock.lock();
        try {
            _connected.signal();
        } finally {
            _lock.unlock();
        }
        // put something in the queue in case the worker thread hangs in
        // _pendingCalls.take()
        _pendingCalls.offer(new HessianRPCCallMessage(null, null, null, null));
        if (_disconnectedListener != null)
            _executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        _disconnectedListener.run();
                    } catch (Throwable ex) {
                        Constants.ahessianLogger.warn("", ex);
                    }
                }
            });

        super.channelInactive(ctx);

    }

    /**
     * Sets the done listener. This listener is fired whenever all requests have
     * been completed
     * 
     * @param listener
     *            the new listener
     */
    public void setDoneListener(Runnable listener) {
        _doneListener = listener;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
        ahessianLogger.warn("error accessing service " + _name + " Exception " + e.getClass() + " "
                + e.getCause().getMessage());
        ctx.channel().disconnect();
        ctx.channel().close();
        if (!_stop) {
            _channel = null;
            // _stop = true;
            _lock.lock();
            try {
                _connected.signal();
            } finally {
                _lock.unlock();
            }
            // put something in the queue in case the worker thread hangs in
            // _pendingCalls.take()
            _pendingCalls.offer(new HessianRPCCallMessage(null, null, null, null));
        }
    }

    public void invalidateProxies() {
        for (Object proxy : new HashSet(_proxies.keySet())) {
            returnProxy(proxy);
        }
    }

    public void setClosedSessionListener(Runnable listener) {
        _closedSessionListener = listener;
    }

    public void setDisconnectedListener(Runnable listener) {
        _disconnectedListener = listener;
    }

    public void setConnectedListener(Runnable listener) {
        _connectedListener = listener;
    }

    public void setNewSessionListener(Runnable listener) {
        _newSessionListener = listener;
    }

    public void invalidateAllPendingCalls() {

        final HessianRPCReplyMessage message = new HessianRPCReplyMessage(null,
                new RuntimeException("connection closed"), null);
        for (Future future : new ArrayList<Future>(_openCalls.values())) {
            ((HessianProxyFuture) future).set(message);
        }
        _openCalls.clear();
        _pendingCalls.clear();
    }

    public void setBlocked(boolean blocked) {
        _blocked = blocked;
    }

}