@Override
 public void startResponse(HttpResponse response) throws Exception {
   try {
     if (LOG.isTraceEnabled()) LOG.trace("started response for /stream");
     _decorated = new ChunkedBodyReadableByteChannel();
     super.startResponse(response);
     if (!_errorHandled) {
       _stateReuse.switchToStreamSuccess(_decorated);
       _callback.enqueueMessage(_stateReuse);
     }
   } catch (Exception e) {
     LOG.error("Error reading events from server", e);
     if (!_errorHandled) {
       _stateReuse.switchToStreamResponseError();
       _callback.enqueueMessage(_stateReuse);
     }
   }
 }
 @Override
 public void handleChannelException(Throwable cause) {
   DbusPrettyLogUtils.logExceptionAtError("Exception during /sources response: ", cause, LOG);
   if (_responseStatus != ResponseStatus.CHUNKS_FINISHED) {
     LOG.info("Enqueueing /sources response error state to puller queue");
     _stateReuse.switchToSourcesResponseError();
     _callback.enqueueMessage(_stateReuse);
   } else {
     LOG.info("Skipping enqueueing /sources response error state to puller queue");
   }
   super.handleChannelException(cause);
 }
 @Override
 public void handleChannelException(Throwable cause) {
   DbusPrettyLogUtils.logExceptionAtError("Exception during /register response: ", cause, LOG);
   if (_responseStatus != ResponseStatus.CHUNKS_FINISHED) {
     if (LOG.isDebugEnabled()) {
       LOG.debug("Enqueueing /register response error state to puller queue");
     }
     _stateReuse.switchToRegisterResponseError();
     _callback.enqueueMessage(_stateReuse);
   } else {
     if (LOG.isDebugEnabled()) {
       LOG.debug("Skipping enqueueing /register response error state to puller queue");
     }
   }
   super.handleChannelException(cause);
 }
  @Override
  public void finishResponse() throws Exception {
    super.finishResponse();
    if (_errorHandled) {
      return;
    }

    final String sourcesResponseError = "/sources response error: ";
    try {
      String exceptionName = RemoteExceptionHandler.getExceptionName(_decorated);
      if (null != exceptionName) {
        LOG.error(sourcesResponseError + RemoteExceptionHandler.getExceptionMessage(_decorated));
        _stateReuse.switchToSourcesResponseError();
      } else {
        String hostHdr = DbusConstants.UNKNOWN_HOST;
        String svcHdr = DbusConstants.UNKNOWN_SERVICE_ID;
        if (null != getParent()) {
          hostHdr = getParent().getRemoteHost();
          svcHdr = getParent().getRemoteService();
          LOG.info("initiated sesssion to host " + hostHdr + " service " + svcHdr);
        }

        InputStream bodyStream = Channels.newInputStream(_decorated);
        ObjectMapper mapper = new ObjectMapper();

        List<IdNamePair> sources =
            mapper.readValue(bodyStream, new TypeReference<List<IdNamePair>>() {});
        _stateReuse.switchToSourcesSuccess(sources, hostHdr, svcHdr);
      }
    } catch (IOException ex) {
      LOG.error(sourcesResponseError, ex);
      _stateReuse.switchToSourcesResponseError();
    } catch (RuntimeException ex) {
      LOG.error(sourcesResponseError, ex);
      _stateReuse.switchToSourcesResponseError();
    }

    _callback.enqueueMessage(_stateReuse);
  }
  private void onRequestFailure(String req, Throwable cause) {
    LOG.info("request failure: req=" + req + "  cause=" + cause);

    // special case - DDSDBUS-1497
    // in case of WriteTimeoutException we will get close channel exception.
    // Since the timeout exception comes from a different thread (timer) we
    // may end up informing PullerThread twice - and causing it to create two new connections
    // Instead we just drop this exception and just react to close channel
    if (shouldIgnoreWriteTimeoutException(cause)) {
      LOG.error("got RequestFailure because of WriteTimeoutException");
      return;
    }
    switch (_curState) {
      case SOURCES_REQUEST_CONNECT:
      case SOURCES_REQUEST_WRITE:
        {
          _callbackStateReuse.switchToSourcesRequestError();
          break;
        }
      case REGISTER_REQUEST_CONNECT:
      case REGISTER_REQUEST_WRITE:
        {
          _callbackStateReuse.switchToRegisterRequestError();
          break;
        }
      case STREAM_REQUEST_CONNECT:
      case STREAM_REQUEST_WRITE:
        {
          _callbackStateReuse.switchToStreamRequestError();
          break;
        }
      default:
        throw new RuntimeException("don't know what to do in state:" + _curState);
    }

    _callback.enqueueMessage(_callbackStateReuse);
  }
  @Override
  public void finishResponse() throws Exception {
    super.finishResponse();
    if (_errorHandled) {
      return;
    }

    final String registerResponseError = "/register response error: ";
    try {
      String exceptionName = RemoteExceptionHandler.getExceptionName(_decorated);
      if (null != exceptionName) {
        LOG.error(registerResponseError + RemoteExceptionHandler.getExceptionMessage(_decorated));
        _stateReuse.switchToRegisterResponseError();
      } else {
        InputStream bodyStream = Channels.newInputStream(_decorated);
        ObjectMapper mapper = new ObjectMapper();
        int registerResponseVersion = 3; // either 2 or 3 would suffice here; we care only about 4

        if (_registerResponseVersionHdr != null) {
          try {
            registerResponseVersion = Integer.parseInt(_registerResponseVersionHdr);
          } catch (NumberFormatException e) {
            throw new RuntimeException(
                "Could not parse /register response protocol version: "
                    + _registerResponseVersionHdr);
          }
          if (registerResponseVersion < 2 || registerResponseVersion > 4) {
            throw new RuntimeException(
                "Out-of-range /register response protocol version: " + _registerResponseVersionHdr);
          }
        }

        if (registerResponseVersion == 4) // DDSDBUS-2009
        {
          HashMap<String, List<Object>> responseMap =
              mapper.readValue(bodyStream, new TypeReference<HashMap<String, List<Object>>>() {});

          // Look for mandatory SOURCE_SCHEMAS_KEY.
          Map<Long, List<RegisterResponseEntry>> sourcesSchemasMap =
              RegisterResponseEntry.createFromResponse(
                  responseMap, RegisterResponseEntry.SOURCE_SCHEMAS_KEY, false);
          // Look for optional KEY_SCHEMAS_KEY
          // Key schemas, if they exist, should correspond to source schemas, but it's not
          // a one-to-one mapping.  The same version of a key schema may be used for several
          // versions of a source schema, or vice versa.  (The IDs must correspond.)
          //
          // TODO (DDSDBUS-xxx):  support key schemas on the relay side, too
          Map<Long, List<RegisterResponseEntry>> keysSchemasMap =
              RegisterResponseEntry.createFromResponse(
                  responseMap, RegisterResponseEntry.KEY_SCHEMAS_KEY, true);

          // Look for optional METADATA_SCHEMAS_KEY
          List<RegisterResponseMetadataEntry> metadataSchemasList =
              RegisterResponseMetadataEntry.createFromResponse(
                  responseMap, RegisterResponseMetadataEntry.METADATA_SCHEMAS_KEY, true);

          _stateReuse.switchToRegisterSuccess(
              sourcesSchemasMap, keysSchemasMap, metadataSchemasList);
        } else // version 2 or 3
        {
          List<RegisterResponseEntry> schemasList =
              mapper.readValue(bodyStream, new TypeReference<List<RegisterResponseEntry>>() {});

          Map<Long, List<RegisterResponseEntry>> sourcesSchemasMap =
              RegisterResponseEntry.convertSchemaListToMap(schemasList);

          _stateReuse.switchToRegisterSuccess(sourcesSchemasMap, null, null);
        }
      }
    } catch (IOException ex) {
      LOG.error(registerResponseError, ex);
      _stateReuse.switchToRegisterResponseError();
    } catch (RuntimeException ex) {
      LOG.error(registerResponseError, ex);
      _stateReuse.switchToRegisterResponseError();
    }

    _callback.enqueueMessage(_stateReuse);
  }