com.cloudhopper.smpp.impl.UnboundSmppSession.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudhopper.smpp.impl.UnboundSmppSession.java

Source

package com.cloudhopper.smpp.impl;

/*
 * #%L
 * ch-smpp
 * %%
 * Copyright (C) 2009 - 2012 Cloudhopper by Twitter
 * %%
 * 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.
 * #L%
 */

import com.cloudhopper.smpp.SmppBindType;
import com.cloudhopper.smpp.SmppConstants;
import com.cloudhopper.smpp.SmppSessionConfiguration;
import com.cloudhopper.smpp.channel.ChannelUtil;
import com.cloudhopper.smpp.pdu.*;
import com.cloudhopper.smpp.type.LoggingOptions;
import com.cloudhopper.smpp.type.SmppChannelException;
import com.cloudhopper.smpp.type.SmppProcessingException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.TimerTask;

/**
 * Handles new channels connected via the SmppServer that are not yet properly
 * "bound" (authenticated).  An unbound session handles some of the logic to
 * authenticate a channel, then do final setups of an SmppSession, and handoff
 * a prepared session to a server handler.
 * 
 * @author joelauer (twitter: @jjlauer or <a href="http://twitter.com/jjlauer" target=window>http://twitter.com/jjlauer</a>)
 */
public class UnboundSmppSession implements SmppSessionChannelListener {
    private static final Logger logger = LoggerFactory.getLogger(UnboundSmppSession.class);

    // the channel that's not "bound" yet as an SMPP session
    private final String channelName;
    private final Channel channel;
    private final BindTimeoutTask bindTimeoutTask;
    private final DefaultSmppServer server;

    public UnboundSmppSession(String channelName, Channel channel, DefaultSmppServer server) {
        this.channelName = channelName;
        this.channel = channel;
        this.server = server;
        // schedule the timer to close the channel after X milliseconds
        this.bindTimeoutTask = new BindTimeoutTask();
        this.server.getBindTimer().schedule(bindTimeoutTask, this.server.getConfiguration().getBindTimeout());
    }

    // called when a PDU is received and decoded on the channel
    @Override
    public void firePduReceived(Pdu pdu) {
        // always log the PDU received on an unbound session
        logger.info("received PDU: {}", pdu);

        // only bind requests are permitted
        if (!(pdu instanceof BaseBind)) {
            logger.warn("Only bind requests are permitted on new connections, closing connection [{}]",
                    channelName);

            // FIXME: we could create a response with an error and THEN close the connection...

            // cancel the timer task & close connection
            closeChannelAndCancelTimer();
            return;
        }

        // delegate any bind request to the server handler
        // variables we track for a successful bind request
        BaseBind bindRequest = (BaseBind) pdu;

        // create a default session configuration based on this bind request
        SmppSessionConfiguration sessionConfiguration = createSessionConfiguration(bindRequest);

        // assign a new identifier for this session
        Long sessionId = server.nextSessionId();

        try {
            // delegate the bind request upstream to server handler
            this.server.bindRequested(sessionId, sessionConfiguration, bindRequest);
        } catch (SmppProcessingException e) {
            logger.warn("Bind request rejected or failed for connection [{}] with error [{}]", channelName,
                    e.getMessage());
            // create a failed bind response and send back to connection
            BaseBindResp bindResponse = server.createBindResponse(bindRequest, e.getErrorCode());
            this.sendResponsePdu(bindResponse);
            // cancel the timer task & close connection
            closeChannelAndCancelTimer();
            return;
        }

        // if we got there then 98% "bound" -- we just need to create the
        // new session and tie everything together -- cancel the bind timer
        this.bindTimeoutTask.cancel();

        // prepare an "OK" bind response that the session will send back once flagged as 'serverReady'
        BaseBindResp preparedBindResponse = server.createBindResponse(bindRequest, SmppConstants.STATUS_OK);

        try {
            // create a new a new session and tie the bind response to it
            server.createSession(sessionId, channel, sessionConfiguration, preparedBindResponse);
        } catch (SmppProcessingException e) {
            logger.warn("Bind request was approved, but createSession failed for connection [{}] with error [{}]",
                    channelName, e.getMessage());
            // create a failed bind response and send back to connection
            BaseBindResp bindResponse = server.createBindResponse(bindRequest, e.getErrorCode());
            this.sendResponsePdu(bindResponse);
            // cancel the timer task & close connection
            closeChannelAndCancelTimer();
            return;
        }
    }

    public void closeChannelAndCancelTimer() {
        // if the channel is being closed, we should always make sure the timer
        // bind task is always cancelled as well
        this.bindTimeoutTask.cancel();
        // close the channel
        this.channel.close();
    }

    @Override
    public void fireExceptionThrown(Throwable t) {
        logger.warn("Exception thrown, closing connection [{}]: {}", channelName, t);
        closeChannelAndCancelTimer();
    }

    @Override
    public void fireChannelClosed() {
        logger.info("Connection closed with [{}]", channelName);
        closeChannelAndCancelTimer();
    }

    protected SmppSessionConfiguration createSessionConfiguration(BaseBind bindRequest) {
        SmppSessionConfiguration sessionConfiguration = new SmppSessionConfiguration();
        sessionConfiguration
                .setName("SmppServerSession." + bindRequest.getSystemId() + "." + bindRequest.getSystemType());
        sessionConfiguration.setSystemId(bindRequest.getSystemId());
        sessionConfiguration.setPassword(bindRequest.getPassword());
        sessionConfiguration.setSystemType(bindRequest.getSystemType());
        sessionConfiguration.setBindTimeout(server.getConfiguration().getBindTimeout());
        sessionConfiguration.setAddressRange(bindRequest.getAddressRange());
        sessionConfiguration.setHost(ChannelUtil.getChannelRemoteHost(channel));
        sessionConfiguration.setPort(ChannelUtil.getChannelRemotePort(channel));
        sessionConfiguration.setInterfaceVersion(bindRequest.getInterfaceVersion());

        LoggingOptions loggingOptions = new LoggingOptions();
        loggingOptions.setLogPdu(true);
        sessionConfiguration.setLoggingOptions(loggingOptions);

        // handle all 3 types...
        if (bindRequest instanceof BindTransceiver) {
            sessionConfiguration.setType(SmppBindType.TRANSCEIVER);
        } else if (bindRequest instanceof BindReceiver) {
            sessionConfiguration.setType(SmppBindType.RECEIVER);
        } else if (bindRequest instanceof BindTransmitter) {
            sessionConfiguration.setType(SmppBindType.TRANSMITTER);
        }

        // new default options set from server config
        sessionConfiguration.setWindowSize(server.getConfiguration().getDefaultWindowSize());
        sessionConfiguration.setWindowWaitTimeout(server.getConfiguration().getDefaultWindowWaitTimeout());
        sessionConfiguration.setWindowMonitorInterval(server.getConfiguration().getDefaultWindowMonitorInterval());
        sessionConfiguration.setRequestExpiryTimeout(server.getConfiguration().getDefaultRequestExpiryTimeout());
        sessionConfiguration.setCountersEnabled(server.getConfiguration().isDefaultSessionCountersEnabled());

        return sessionConfiguration;
    }

    public void sendResponsePdu(PduResponse pdu) {
        try {
            // encode the pdu into a buffer
            ByteBuf buffer = server.getTranscoder().encode(pdu);

            // always log the PDU
            logger.info("send PDU: {}", pdu);

            // write the pdu out & wait till its written
            ChannelFuture channelFuture = this.channel.writeAndFlush(buffer).await();

            // check if the write was a success
            if (!channelFuture.isSuccess()) {
                // the write failed, make sure to throw an exception
                throw new SmppChannelException(channelFuture.cause().getMessage(), channelFuture.cause());
            }
        } catch (Exception e) {
            logger.error("Fatal exception thrown while attempting to send response PDU: {}", e);
        }
    }

    /**
     * Simple task that closes a channel if its not bound within a certain time.
     */
    private final class BindTimeoutTask extends TimerTask {
        @Override
        public void run() {
            logger.warn("Channel not bound within [{}] ms, closing connection [{}]",
                    server.getConfiguration().getBindTimeout(), channelName);
            channel.close();
            this.cancel();
            server.getCounters().incrementBindTimeoutsAndGet();
        }
    }
}