com.mellanox.jxio.ServerSession.java Source code

Java tutorial

Introduction

Here is the source code for com.mellanox.jxio.ServerSession.java

Source

/*
 ** Copyright (C) 2013 Mellanox Technologies
 **
 ** 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 com.mellanox.jxio;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.mellanox.jxio.impl.Bridge;
import com.mellanox.jxio.impl.Event;
import com.mellanox.jxio.impl.EventMsgError;
import com.mellanox.jxio.impl.EventNewMsg;
import com.mellanox.jxio.impl.EventSession;
import com.mellanox.jxio.impl.EventNameImpl;
import com.mellanox.jxio.exceptions.*;

/**
 * ServerSession is the object which receives Msgs from Client and sends responses. This side
 * does not initiate connection. ServerSession receives several events on his lifetime.
 * On each of them a method of interface Callbacks is invoked.
 * User must implement this interface and pass it in c-tor.
 * The events are:
 * 1. onRequest
 * 2. onSessionEvent
 * 3. onMsgError
 * 
 */
public class ServerSession extends EventQueueHandler.Eventable {

    /*
     * events that are session related will arrive on eqh that received the original
     * onSessionNew events. msg events will arrive on the eqh to which the session was
     * forwarded
     */
    private final Callbacks callbacks;
    private final String uri;
    private final String name;
    private final String nameForLog;
    private EventQueueHandler eqhMsg;
    private EventQueueHandler eqhSession;
    private ServerPortal creator;
    private int msgsInUse;
    private boolean receivedClosed = false;
    private static final Log LOG = LogFactory.getLog(ServerSession.class.getCanonicalName());

    public static interface Callbacks {
        /**
         * This event is triggered when a request from Client is received.
         * Server should send the response on the same {@link com.mellanox.jxio.Msg} object.
         * 
         * @param msg
         *            containing Client's request
         */
        public void onRequest(Msg msg);

        /**
         * There are several types of session events: SESSION_CLOSED(because user called ServerSession.close(),
         * Client initiated close or because of an internal error),
         * SESSION_ERROR (due to internal error)
         * 
         * @param event
         *            - the event that was triggered
         * @param reason
         *            - the object containing the reason for triggering event
         */
        public void onSessionEvent(EventName event, EventReason reason);

        /**
         * This event is triggered if there is an error in Msg send/receive. The method returns true
         * if the Msg should be released automatically once onMsgError finishes and false if
         * the user will release it later with method returnOnMsgError.
         * 
         * @param msg
         *            - send/receive of this Msg failed
         * @param reason
         *            - reason of the msg error
         * @return true if the Msg should be released automatically once onMsgError finishes and false
         *         if the user will release it later with method returnOnMsgError.
         */
        public boolean onMsgError(Msg msg, EventReason reason);
    }

    /**
     * Constructor of ServerSession. This object should be created after ServerPortal receives
     * callback onNewSession.
     * 
     * @param sessionKey
     *            - was received in ServerPortal's callback onNewSession
     * @param callbacks
     *            - implementation of Interface ServerSession.Callbacks
     */
    public ServerSession(SessionKey sessionKey, Callbacks callbacks) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("SS CTOR entry");
        }
        this.callbacks = callbacks;
        setId(sessionKey.getSessionPtr());
        this.uri = sessionKey.getUri();
        this.name = "jxio.SS[" + Long.toHexString(getId()) + "]";
        this.nameForLog = this.name + ": ";

        if (LOG.isDebugEnabled()) {
            LOG.debug(this.toLogString() + "listening to " + sessionKey.getUri());

            LOG.debug(this.toLogString() + "SS CTOR done");
        }
    }

    /**
     * This method closes the ServerSession.
     * <p>
     * The method is asynchronous: the ServerSession will be closed only when it receives event SESSION_CLOSED
     * 
     * @return true if there was a successful call to close of the ServerSession object on C side and false otherwise
     */
    public boolean close() {
        if (this.getIsClosing()) {
            LOG.warn(this.toLogString()
                    + "attempting to close server session that is already closed or being closed");
            return false;
        }
        if (getId() == 0) {
            LOG.error(this.toLogString() + "closing ServerSession with empty id");
            return false;
        }
        boolean ret = Bridge.closeServerSession(getId());
        setIsClosing(ret);
        if (ret) {
            if (LOG.isDebugEnabled())
                LOG.debug(this.toLogString() + "close() Done successfully");
        } else
            LOG.warn(this.toLogString() + "close server session failed");

        return ret;
    }

    /**
     * This method sends the response to client.
     * <p>
     * The send is asynchronous, therefore even if the function returns, this does not mean that the msg reached the client or even was sent to the
     * client. The size send to Client is the current position of the OUT ByteBuffer
     * 
     * @param msg
     *            - Msg to be sent to Client
     * @throws JxioSessionClosedException if session already closed. In case exception is thrown, discardRequest 
     * must be called for this message after SESSION_CLOSED   event is officially recieved. 
     * @throws JxioGeneralException if send failed for any other reason
     */
    public void sendResponse(Msg msg) throws JxioGeneralException, JxioSessionClosedException {
        if (this.getIsClosing()) {
            LOG.debug(this.toLogString() + "Trying to send message " + msg + "while session is closing");
            throw new JxioSessionClosedException("sendResponse");
        }
        if (this.getId() == 0) {
            LOG.warn(this.toLogString() + "Trying to send message on non established session");
            throw new JxioGeneralException(EventReason.JXIO_GENERAL_ERROR, "sendResponse");
        }
        int ret = Bridge.serverSendResponse(msg.getId(), msg.getOut().position(), this.getId());
        if (ret > 0) {
            if (ret != EventReason.SESSION_DISCONNECTED.getIndex()) {
                LOG.debug(this.toLogString() + "there was an error sending the message because of reason " + ret);
                LOG.debug(this.toLogString() + "unhandled exception. reason is " + ret);
                throw new JxioGeneralException(ret, "sendResponse");
            } else {
                setIsClosing(true);
                LOG.debug(this.toLogString() + "message send failed because the session is already closed!");
                throw new JxioSessionClosedException("sendResponse");
            }
        }
        this.msgsInUse--;
        /*
         * this message should be released back to pool.
         * even though the message might not reached the client yet, it's ok since this pool is
         * used only for matching of id to object. the actual release to pool is done on c side
         */
        this.eqhMsg.releaseMsgBackToPool(msg);
    }

    /**
     * This method releases Msg to pool after onMsgError. In case the user returns false in
     * onMsgError he needs to release the Msg back to pool once he is done with it (using returnOnMsgError)
     * 
     * @param msg
     *            - msg to be released back to pool
     */
    public void returnOnMsgError(Msg msg) {
        // the user finished with the Msg. It can now be released on C side
        Bridge.releaseMsgServerSide(msg.getId());
        this.eqhMsg.releaseMsgBackToPool(msg);
        this.msgsInUse--;
    }

    /** 
     * This method releases Msg to pool once session is closing (getIsClosing()==true or 
     * after SESSION_CLOSED event). This method should be called
     * for msgs that were not in pool in time of SESSION_CLOSED event arrival 
     * @param msg - msg to be discarded
     * @return true if msg was discarded and false otherwise
     */
    public boolean discardRequest(Msg msg) {
        if (this.msgsInUse == 0 || !this.getIsClosing())
            return false; //to avoid release twice of session server
        Bridge.discardRequest(msg.getId());
        this.eqhMsg.releaseMsgBackToPool(msg);
        this.msgsInUse--;
        if ((this.msgsInUse == 0) && this.getReceivedClosed()) {
            if (LOG.isDebugEnabled())
                LOG.debug(this.toLogString() + "all msgs were discarded. Can delete SessionServer");
            Bridge.deleteSessionServer(this.getId());
        }
        return true;
    }

    final String getUri() {
        return uri;
    }

    void setEventQueueHandlers(EventQueueHandler eqhS, EventQueueHandler eqhM) {
        this.eqhMsg = eqhM;
        this.eqhMsg.addEventable(this);
        this.eqhSession = eqhS;
        this.eqhSession.addEventable(this); // if eqhS==eqhM, EventQueueHandler.eventables will contain only
                                            // one value
    }

    void setPortal(ServerPortal p) {
        this.creator = p;
    }

    boolean onEvent(Event ev) {
        switch (ev.getEventType()) {
        case 0: // session event
            if (ev instanceof EventSession) {
                int errorType = ((EventSession) ev).getErrorType();
                int reason = ((EventSession) ev).getReason();
                if (LOG.isDebugEnabled()) {
                    LOG.debug(this.toLogString() + "Received Session Event: Type=" + errorType + ", Reason="
                            + reason);
                }
                EventNameImpl eventName = EventNameImpl.getEventByIndex(errorType);
                switch (eventName) {
                // Internal event
                case CONNECTION_CLOSED:
                case CONNECTION_DISCONNECTED:
                    this.setIsClosing(true);
                    return false;

                case SESSION_CLOSED:
                    this.setIsClosing(true);
                    this.setReceivedClosed(true);
                    // now that the user knows session is closed, object holding session state can be deleted
                    if (this.msgsInUse == 0) {
                        if (LOG.isDebugEnabled())
                            LOG.debug(this.toLogString() + "there are no msgs in use, can delete SessionServer");
                        Bridge.deleteSessionServer(this.getId());
                    } else {
                        if (LOG.isDebugEnabled())
                            LOG.debug(this.toLogString() + "there are still " + this.msgsInUse
                                    + " msgs in use. Can not delete SessionServer");
                    }
                    EventName eventNameForApp = EventName.getEventByIndex(eventName.getPublishedIndex());
                    EventReason eventReason = EventReason.getEventByXioIndex(reason);
                    try {
                        callbacks.onSessionEvent(eventNameForApp, eventReason);
                    } catch (Exception e) {
                        eqhMsg.setCaughtException(e);
                        LOG.debug(this.toLogString() + "[onSessionEvent] Callback exception occurred. Event was "
                                + eventName.toString());
                    }
                    return true;

                //internal event
                case SESSION_TEARDOWN:
                    // now we are officially done with this session and it can  be deleted from the EQH
                    removeFromEQHs();
                    // need to delete this Session from the set in ServerPortal
                    this.creator.removeSession(this);
                    return false;

                default:
                    break;
                }
                LOG.error(this.toLogString() + "Received an un-hadnled event type = " + eventName);
            }
            break;

        case 1: // msg error
            EventMsgError evMsgErr;
            if (ev instanceof EventMsgError) {
                evMsgErr = (EventMsgError) ev;
                Msg msg = evMsgErr.getMsg();
                int reason = evMsgErr.getReason();
                if (LOG.isDebugEnabled()) {
                    LOG.debug(this.toLogString() + "Received Msg Error Event: Msg=" + msg.toString() + ", Reason="
                            + reason);
                }
                EventReason eventReason = EventReason.getEventByXioIndex(reason);
                this.msgsInUse++;
                try {
                    if (callbacks.onMsgError(msg, eventReason)) {
                        // the user is finished with the Msg and it can be released
                        this.returnOnMsgError(msg);
                    }
                } catch (Exception e) {
                    eqhMsg.setCaughtException(e);
                    LOG.debug(this.toLogString() + " [onMsgError] Callback exception occurred. Msg was "
                            + msg.toString());
                }
                return true;
            }
            LOG.error(this.toLogString() + "Event is not an instance of EventMsgError");
            break;

        case 4: // on request
            EventNewMsg evNewMsg;
            if (ev instanceof EventNewMsg) {
                evNewMsg = (EventNewMsg) ev;
                Msg msg = evNewMsg.getMsg();
                if (LOG.isTraceEnabled()) {
                    LOG.trace(this.toLogString() + "Received Msg Event: onRequest Msg=" + msg.toString());
                }
                this.msgsInUse++;
                try {
                    callbacks.onRequest(msg);
                } catch (Exception e) {
                    eqhMsg.setCaughtException(e);
                    LOG.debug(this.toLogString() + "[onRequest] Callback exception occurred. Msg was "
                            + msg.toString());
                }
                return true;
            }
            LOG.error(this.toLogString() + "Event is not an instance of EventNewMsg");
            break;

        default:
            break;
        }
        LOG.error(this.toLogString() + "Received an un-handled event " + ev.getEventType());
        return false;
    }

    private void removeFromEQHs() {
        eqhSession.removeEventable(this);
        if (eqhSession != eqhMsg) {
            eqhMsg.removeEventable(this);
        }
    }

    boolean canClose() {
        if (this.msgsInUse == 0)
            return true;
        LOG.warn(this.toLogString() + "can't be closed. there are " + this.msgsInUse + " waiting to be discarded");
        return false;
    }

    public String toString() {
        return this.name;
    }

    private String toLogString() {
        return this.nameForLog;
    }

    private void setReceivedClosed(boolean receivedClosed) {
        this.receivedClosed = receivedClosed;
    }

    private boolean getReceivedClosed() {
        return receivedClosed;
    }

    /**
     * This class holds the ID of a session. It is passed to user on onNewSession callback
     * and passed to ServerSession's constructor. It contains id of the session request (long) and
     * uri that the client wishes to connect to.
     */
    public static class SessionKey {
        private final long sessionPtr;
        private final String uri;

        /**
         * Returns id of the session request
         * 
         * @return id of the session request
         */
        public long getSessionPtr() {
            return sessionPtr;
        }

        /**
         * Returns uri that the client wishes to connect to
         * 
         * @return uri that the client wishes to connect to
         */
        public String getUri() {
            return uri;
        }

        SessionKey(long sessionPtr, String uri) {
            this.sessionPtr = sessionPtr;
            this.uri = uri;
        }

    }
}