/**
  * Opens an asynchronous server-socket channel.
  *
  * <p>The new channel is created by invoking the {@link
  * AsynchronousChannelProvider#openAsynchronousServerSocketChannel
  * openAsynchronousServerSocketChannel} method on the {@link AsynchronousChannelProvider} object
  * that created the given group. If the group parameter is {@code null} then the resulting channel
  * is created by the system-wide default provider, and bound to the <em>default group</em>.
  *
  * @param group the group to which the newly constructed channel should be bound, or {@code null}
  *     for the default group
  * @return a new asynchronous server socket channel
  * @throws ShutdownChannelGroupException the specified group is shutdown
  * @throws IOException if an I/O error occurs
  */
 public static AsynchronousServerSocketChannel open(AsynchronousChannelGroup group)
     throws IOException {
   return AsynchronousChannelProvider.provider().openAsynchronousServerSocketChannel(group);
 }
  /**
   * Constructs an instance of this class with the specified properties.
   *
   * @param properties service properties
   * @param systemRegistry system registry
   * @param txnProxy transaction proxy
   * @throws Exception if a problem occurs when creating the service
   */
  public ClientSessionServiceImpl(
      Properties properties, ComponentRegistry systemRegistry, TransactionProxy txnProxy)
      throws Exception {
    super(properties, systemRegistry, txnProxy, logger);

    logger.log(Level.CONFIG, "Creating ClientSessionServiceImpl properties:{0}", properties);
    PropertiesWrapper wrappedProps = new PropertiesWrapper(properties);

    try {
      appPort = wrappedProps.getRequiredIntProperty(StandardProperties.APP_PORT, 1, 65535);

      /*
       * Get the property for controlling session event processing.
       */
      eventsPerTxn =
          wrappedProps.getIntProperty(
              EVENTS_PER_TXN_PROPERTY, DEFAULT_EVENTS_PER_TXN, 1, Integer.MAX_VALUE);

      readBufferSize =
          wrappedProps.getIntProperty(
              READ_BUFFER_SIZE_PROPERTY, DEFAULT_READ_BUFFER_SIZE, 8192, Integer.MAX_VALUE);

      writeBufferSize =
          wrappedProps.getIntProperty(
              WRITE_BUFFER_SIZE_PROPERTY, DEFAULT_WRITE_BUFFER_SIZE, 8192, Integer.MAX_VALUE);

      /*
       * Export the ClientSessionServer.
       */
      int serverPort =
          wrappedProps.getIntProperty(SERVER_PORT_PROPERTY, DEFAULT_SERVER_PORT, 0, 65535);
      serverImpl = new SessionServerImpl();
      exporter = new Exporter<ClientSessionServer>(ClientSessionServer.class);
      try {
        int port = exporter.export(serverImpl, serverPort);
        serverProxy = exporter.getProxy();
        if (logger.isLoggable(Level.CONFIG)) {
          logger.log(Level.CONFIG, "export successful. port:{0,number,#}", port);
        }
      } catch (Exception e) {
        try {
          exporter.unexport();
        } catch (RuntimeException re) {
        }
        throw e;
      }

      /*
       * Get services and initialize service-related and other
       * instance fields.
       */
      identityManager = systemRegistry.getComponent(IdentityCoordinator.class);
      flushContextsThread.start();
      contextFactory = new ContextFactory(txnProxy);
      watchdogService = txnProxy.getService(WatchdogService.class);
      nodeMapService = txnProxy.getService(NodeMappingService.class);
      taskService = txnProxy.getService(TaskService.class);
      localNodeId = watchdogService.getLocalNodeId();
      watchdogService.addRecoveryListener(new ClientSessionServiceRecoveryListener());
      int acceptorBacklog =
          wrappedProps.getIntProperty(ACCEPTOR_BACKLOG_PROPERTY, DEFAULT_ACCEPTOR_BACKLOG);

      /*
       * Check service version.
       */
      transactionScheduler.runTask(
          new AbstractKernelRunnable() {
            public void run() {
              checkServiceVersion(VERSION_KEY, MAJOR_VERSION, MINOR_VERSION);
            }
          },
          taskOwner);

      /*
       * Store the ClientSessionServer proxy in the data store.
       */
      transactionScheduler.runTask(
          new AbstractKernelRunnable() {
            public void run() {
              dataService.setServiceBinding(
                  getClientSessionServerKey(localNodeId),
                  new ManagedSerializable<ClientSessionServer>(serverProxy));
            }
          },
          taskOwner);

      /*
       * Listen for incoming client connections.
       */
      InetSocketAddress listenAddress = new InetSocketAddress(appPort);
      AsynchronousChannelProvider provider =
          // TODO fetch from config
          AsynchronousChannelProvider.provider();
      asyncChannelGroup =
          // TODO fetch from config
          provider.openAsynchronousChannelGroup(Executors.newCachedThreadPool());
      acceptor = provider.openAsynchronousServerSocketChannel(asyncChannelGroup);
      try {
        acceptor.bind(listenAddress, acceptorBacklog);
        if (logger.isLoggable(Level.CONFIG)) {
          logger.log(Level.CONFIG, "bound to port:{0,number,#}", getListenPort());
        }
      } catch (Exception e) {
        logger.logThrow(Level.WARNING, e, "acceptor failed to listen on {0}", listenAddress);
        try {
          acceptor.close();
        } catch (IOException ioe) {
          logger.logThrow(Level.WARNING, ioe, "problem closing acceptor");
        }
        throw e;
      }
      // TBD: listen for UNRELIABLE connections as well?

    } catch (Exception e) {
      if (logger.isLoggable(Level.CONFIG)) {
        logger.logThrow(Level.CONFIG, e, "Failed to create ClientSessionServiceImpl");
      }
      doShutdown();
      throw e;
    }
  }