/**
  * Creates a new default SmppServer. Window monitoring and automatic expiration of requests will
  * be disabled with no monitorExecutors. A "cachedDaemonThreadPool" will be used for IO worker
  * threads.
  *
  * @param configuration The server configuration to create this server with
  * @param serverHandler The handler implementation for handling bind requests and
  *     creating/destroying sessions.
  */
 public DefaultSmppServer(SmppServerConfiguration configuration, SmppServerHandler serverHandler) {
   this(
       configuration,
       serverHandler,
       null,
       configuration.isNonBlockingSocketsEnabled()
           ? new NioEventLoopGroup()
           : new OioEventLoopGroup(),
       configuration.isNonBlockingSocketsEnabled()
           ? new NioEventLoopGroup()
           : new OioEventLoopGroup());
 }
  /**
   * Creates a new default SmppServer.
   *
   * @param configuration The server configuration to create this server with
   * @param serverHandler The handler implementation for handling bind requests and
   *     creating/destroying sessions.
   * @param monitorExecutor The scheduled executor that all sessions will share to monitor
   *     themselves and expire requests. If null monitoring will be disabled.
   * @param bossGroup Specify the EventLoopGroup to accept new connections and handle accepted
   *     connections. The {@link EventLoopGroup} is used to handle all the events and IO for {@link
   *     SocketChannel}.
   * @param workerGroup The {@link EventLoopGroup} is used to handle all the events and IO for
   *     {@link Channel}.
   */
  public DefaultSmppServer(
      final SmppServerConfiguration configuration,
      SmppServerHandler serverHandler,
      ScheduledExecutorService monitorExecutor,
      EventLoopGroup bossGroup,
      EventLoopGroup workerGroup) {
    this.configuration = configuration;
    // the same group we'll put every server channel
    this.channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    this.serverHandler = serverHandler;

    // tie the server bootstrap to this server socket channel factory
    this.serverBootstrap = new ServerBootstrap();

    // a factory for creating channels (connections)
    if (configuration.isNonBlockingSocketsEnabled()) {
      this.serverBootstrap.channel(NioServerSocketChannel.class);
    } else {
      this.serverBootstrap.channel(OioServerSocketChannel.class);
    }

    this.bossGroup = bossGroup;
    this.workerGroup = workerGroup;
    this.serverBootstrap.group(this.bossGroup, this.workerGroup);

    // set options for the server socket that are useful
    this.serverBootstrap.option(ChannelOption.SO_REUSEADDR, configuration.isReuseAddress());

    // we use the same default pipeline for all new channels - no need for a factory
    this.serverConnector = new SmppServerConnector(channels, this);

    this.serverBootstrap.childHandler(
        new ChannelInitializer<SocketChannel>() {
          @Override
          public void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline()
                .addLast(SmppChannelConstants.PIPELINE_SERVER_CONNECTOR_NAME, serverConnector);
          }
        });

    // a shared timer used to make sure new channels are bound within X milliseconds
    this.bindTimer = new Timer(configuration.getName() + "-BindTimer0", true);
    // NOTE: this would permit us to customize the "transcoding" context for a server if needed
    this.transcoder = new DefaultPduTranscoder(new DefaultPduTranscoderContext());
    this.sessionIdSequence = new AtomicLong(0);
    this.monitorExecutor = monitorExecutor;
    this.counters = new DefaultSmppServerCounters();
    if (configuration.isJmxEnabled()) {
      registerMBean();
    }
  }
  protected void destroySession(Long sessionId, DefaultSmppSession session) {
    // session destroyed, now pass it upstream
    counters.incrementSessionDestroyedAndGet();
    decrementSessionSizeCounters(session);
    serverHandler.sessionDestroyed(sessionId, session);

    // unregister this session as an mbean
    if (configuration.isJmxEnabled()) {
      session.unregisterMBean(
          configuration.getJmxDomain()
              + ":type="
              + configuration.getName()
              + "Sessions,name="
              + sessionId);
    }
  }
 private void registerMBean() {
   if (configuration == null) {
     return;
   }
   if (configuration.isJmxEnabled()) {
     // register the this queue manager as an mbean
     try {
       ObjectName name =
           new ObjectName(configuration.getJmxDomain() + ":name=" + configuration.getName());
       ManagementFactory.getPlatformMBeanServer().registerMBean(this, name);
     } catch (Exception e) {
       // log the error, but don't throw an exception for this datasource
       logger.error("Unable to register DefaultSmppServerMXBean [{}]", configuration.getName(), e);
     }
   }
 }
 @Override
 public void stop() {
   if (this.channels.size() > 0) {
     logger.info(
         "{} currently has [{}] open child channel(s) that will be closed as part of stop()",
         configuration.getName(),
         this.channels.size());
   }
   // close all channels still open within this session "bootstrap"
   this.channels.close().awaitUninterruptibly();
   // clean up all external resources
   if (this.serverChannel != null) {
     this.serverChannel.close().awaitUninterruptibly();
     this.serverChannel = null;
   }
   logger.info("{} stopped on SMPP port [{}]", configuration.getName(), configuration.getPort());
 }
  protected BaseBindResp createBindResponse(BaseBind bindRequest, int statusCode) {
    BaseBindResp bindResponse = (BaseBindResp) bindRequest.createResponse();
    bindResponse.setCommandStatus(statusCode);
    bindResponse.setSystemId(configuration.getSystemId());

    // if the server supports an SMPP server version >= 3.4 AND the bind request
    // included an interface version >= 3.4, include an optional parameter with configured
    // sc_interface_version TLV
    if (configuration.getInterfaceVersion() >= SmppConstants.VERSION_3_4
        && bindRequest.getInterfaceVersion() >= SmppConstants.VERSION_3_4) {
      Tlv scInterfaceVersion =
          new Tlv(
              SmppConstants.TAG_SC_INTERFACE_VERSION,
              new byte[] {configuration.getInterfaceVersion()});
      bindResponse.addOptionalParameter(scInterfaceVersion);
    }

    return bindResponse;
  }
  @Override
  public void destroy() {
    this.bindTimer.cancel();
    stop();

    // Shut down all event loops to terminate all threads.
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();

    try {
      // Wait until all threads are terminated.
      bossGroup.terminationFuture().sync();
      workerGroup.terminationFuture().sync();
    } catch (InterruptedException e) {
      // is ok
    }

    this.serverBootstrap = null;
    unregisterMBean();
    logger.info("{} destroyed on SMPP port [{}]", configuration.getName(), configuration.getPort());
  }
  @Override
  public void start() throws SmppChannelException {
    if (isDestroyed()) {
      throw new SmppChannelException("Unable to start: server is destroyed");
    }
    try {
      ChannelFuture f = this.serverBootstrap.bind(new InetSocketAddress(configuration.getPort()));

      // wait until the connection is made successfully
      boolean timeout = !f.await(configuration.getBindTimeout());

      if (timeout || !f.isSuccess())
        throw new SmppChannelException(
            "Can't bind to port "
                + configuration.getPort()
                + " after "
                + configuration.getBindTimeout()
                + " milliseconds");

      logger.info("{} started on SMPP port [{}]", configuration.getName(), configuration.getPort());
      serverChannel = f.channel();
    } catch (ChannelException e) {
      throw new SmppChannelException(e.getMessage(), e);
    } catch (InterruptedException e) {
      throw new SmppChannelException(e.getMessage(), e);
    }
  }
  protected void createSession(
      Long sessionId,
      Channel channel,
      SmppSessionConfiguration config,
      BaseBindResp preparedBindResponse)
      throws SmppProcessingException {
    // NOTE: exactly one PDU (bind request) was read from the channel, we
    // now need to delegate permitting this bind request by calling a method
    // further upstream.  Only after the server-side is completely ready to
    // start processing requests from this session, do we want to actually
    // return the bind response and start reading further requests -- we'll
    // initially block reading from the channel first -- this will be turned
    // back on via the "serverReady()" method call on the session object

    // make sure the channel is not being read/processed (until we flag we're ready later on)
    channel.config().setAutoRead(false);

    // auto negotiate the interface version in use based on the requested interface version
    byte interfaceVersion = this.autoNegotiateInterfaceVersion(config.getInterfaceVersion());

    // create a new server session associated with this server
    DefaultSmppSession session =
        new DefaultSmppSession(
            SmppSession.Type.SERVER,
            config,
            channel,
            this,
            sessionId,
            preparedBindResponse,
            interfaceVersion,
            monitorExecutor);

    // replace name of thread used for renaming
    SmppSessionThreadRenamer threadRenamer =
        (SmppSessionThreadRenamer)
            channel.pipeline().get(SmppChannelConstants.PIPELINE_SESSION_THREAD_RENAMER_NAME);
    threadRenamer.setThreadName(config.getName());

    // add a logging handler after the thread renamer
    SmppSessionLogger loggingHandler =
        new SmppSessionLogger(
            DefaultSmppSession.class.getCanonicalName(), config.getLoggingOptions());
    channel
        .pipeline()
        .addAfter(
            SmppChannelConstants.PIPELINE_SESSION_THREAD_RENAMER_NAME,
            SmppChannelConstants.PIPELINE_SESSION_LOGGER_NAME,
            loggingHandler);

    // decoder in pipeline is ok (keep it)

    // create a new wrapper around a session to pass the pdu up the chain
    channel.pipeline().remove(SmppChannelConstants.PIPELINE_SESSION_WRAPPER_NAME);
    channel
        .pipeline()
        .addLast(
            SmppChannelConstants.PIPELINE_SESSION_WRAPPER_NAME, new SmppSessionWrapper(session));

    // check if the # of channels exceeds maxConnections
    if (this.channels.size() > this.configuration.getMaxConnectionSize()) {
      logger.warn(
          "The current connection size [{}] exceeds the configured max connection size [{}]",
          this.channels.size(),
          this.configuration.getMaxConnectionSize());
    }

    // session created, now pass it upstream
    counters.incrementSessionCreatedAndGet();
    incrementSessionSizeCounters(session);
    this.serverHandler.sessionCreated(sessionId, session, preparedBindResponse);

    // register this session as an mbean
    if (configuration.isJmxEnabled()) {
      session.registerMBean(
          configuration.getJmxDomain()
              + ":type="
              + configuration.getName()
              + "Sessions,name="
              + sessionId);
    }
  }