All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.symphony.bdk.bot.sdk.sse.SseController Maven / Gradle / Ivy

package com.symphony.bdk.bot.sdk.sse;

import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import com.symphony.bdk.bot.sdk.sse.config.SseSubscriberProps;
import com.symphony.bdk.bot.sdk.symphony.ConfigClient;

/**
 * Server-sent Event Controller Endpoint that offers all support required for client applications to
 * receive real-time updates from server.
 *
 * @author Marcus Secato
 */
@RestController
public class SseController {
  private static final Logger LOGGER = LoggerFactory.getLogger(SseController.class);
  private static final String SSE_PATH = "events/{eventTypes}";

  @Autowired
  @Qualifier("requestMappingHandlerMapping")
  private RequestMappingHandlerMapping handlerMapping;

  private final SseSubscriberProps subscriberConfig;
  private SsePublisherRouter ssePublisherRouter;
  private String authPath;

  public SseController(SsePublisherRouter ssePublisherRouter,
      ConfigClient configClient, SseSubscriberProps subscriberConfig) {
    this.ssePublisherRouter = ssePublisherRouter;
    this.authPath = configClient.getExtAppAuthPath();
    this.subscriberConfig = subscriberConfig;
  }

  @PostConstruct
  public void init() throws NoSuchMethodException {
    registerRoute(authPath.concat(SSE_PATH));
  }

  /**
   * SSE endpoint
   *
   * @param eventTypes  the event types that the client wants to listen to
   * @param metadata    metadata to be sent to publishers (e.g. filtering criteria)
   * @param lastEventId ID of last event client application received
   * @param userId      ID of the user subscribing to the specified category
   * @param response    the request response
   * @return the {@link SseEmitter} representing the SSE connection
   */
  public SseEmitter subscribeSse(@PathVariable List eventTypes,
      @RequestParam Map metadata,
      @RequestHeader(name = "Last-Event-ID", required = false) String lastEventId,
      @RequestAttribute(name = "userId", required = false) String userId,
      HttpServletResponse response) {

    List> publishers = ssePublisherRouter.findPublishers(eventTypes);

    if (publishers.isEmpty()) {
      LOGGER.info("No SSE publisher found for event types {}", eventTypes);
      response.setStatus(HttpServletResponse.SC_NOT_FOUND);
      return null;
    }

    SseEmitter emitter = new SseEmitter();
    SseSubscriber subscriber = new SseSubscriber(emitter, eventTypes,
        metadata, lastEventId, parseUserId(userId), subscriberConfig);

    try {
      ssePublisherRouter.bind(subscriber, publishers);
    } catch (TaskRejectedException tre) {
      LOGGER.info("Rejecting subscription. No more threads in SSE pool.");
      throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE);
    }

    return emitter;
  }

  private Long parseUserId(String userId) {
    Long uid = null;
    if (userId != null) {
      try {
        uid = new Long(userId);
      } catch (NumberFormatException nfe) {
        LOGGER.info("Rejecting subscription. Failed to parse user id {}", userId);
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
      }
    }
    return uid;
  }

  private void registerRoute(String route) throws NoSuchMethodException {
    handlerMapping.registerMapping(RequestMappingInfo.paths(route)
            .methods(RequestMethod.GET)
            .produces(MediaType.TEXT_EVENT_STREAM_VALUE).build(),
        this,
        SseController.class.getDeclaredMethod("subscribeSse",
            List.class,
            Map.class,
            String.class,
            String.class,
            HttpServletResponse.class));
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy