/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.web.analytics.rest; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.id.UniqueId; import com.opengamma.web.analytics.push.ConnectionManager; import com.opengamma.web.analytics.push.WebPushServletContextUtils; import com.sun.jersey.api.core.HttpContext; import com.sun.jersey.api.model.AbstractMethod; import com.sun.jersey.spi.container.ResourceFilter; import com.sun.jersey.spi.container.ResourceFilterFactory; /** * Creates {@link EntitySubscriptionFilter}s which create subscriptions for push notifications for changes to entities * requested via the REST interface. If a REST method parameter is annotated with {@link Subscribe} it will be * interpreted as a {@link UniqueId} and any changes to the entity will cause an update to be pushed over * the long-polling HTTP interface. The annotated parameter must be a string that can be parsed by * {@link UniqueId#parse(String)} and must also have a {@link PathParam} annotation. */ public class SubscribingFilterFactory implements ResourceFilterFactory { private static final Logger s_logger = LoggerFactory.getLogger(SubscribingFilterFactory.class); /** HTTP context injected by Jersey. This is a proxy that always points to the context for the current request */ @Context private HttpContext _httpContext; /** Servlet context injected by Jersey. This is a proxy that always points to the context for the current request */ @Context private ServletContext _servletContext; /** Request injected by Jersey. This is a proxy that always points to the current request */ @Context private HttpServletRequest _servletRequest; @Override public List<ResourceFilter> create(AbstractMethod abstractMethod) { if (!WebPushServletContextUtils.isConnectionManagerAvailable(_servletContext)) { return Collections.emptyList(); } List<ResourceFilter> filters = new ArrayList<ResourceFilter>(); ResourceFilter entityFilter = createEntitySubscriptionFilter(abstractMethod); if (entityFilter != null) { filters.add(entityFilter); } ResourceFilter masterFilter = createMasterSubscriptionFilter(abstractMethod); if (masterFilter != null) { filters.add(masterFilter); } return filters; } private ConnectionManager getUpdateManager() { return WebPushServletContextUtils.getConnectionManager(_servletContext); } /** * Creates a filter that creates a subscription for an entity when the method is invoked. The method must have a * parameter annotated with {@link Subscribe} and {@link PathParam} which is a string that can be parsed by * {@link UniqueId#parse(String)}. A notification is sent when the object with the specified {@link UniqueId} * changes. * @param abstractMethod A Jersey REST method * @return A filter to set up subscriptions when the method is invoked or null if the method doesn't * need entity subscriptions */ private ResourceFilter createEntitySubscriptionFilter(AbstractMethod abstractMethod) { Method method = abstractMethod.getMethod(); Annotation[][] annotations = method.getParameterAnnotations(); List<String> uidParamNames = new ArrayList<String>(); // find params annotated with @Subscribe. must also have @PathParam for (Annotation[] paramAnnotations : annotations) { boolean subscribe = false; String paramName = null; for (Annotation annotation : paramAnnotations) { if (annotation instanceof Subscribe) { subscribe = true; } else if (annotation instanceof PathParam) { paramName = ((PathParam) annotation).value(); } } if (subscribe) { if (paramName != null) { uidParamNames.add(paramName); } else { s_logger.warn("@Subscribe annotation found without matching @PathParam on method {}.{}(), no subscription " + "will be created", method.getDeclaringClass().getSimpleName(), method.getName()); } } } if (!uidParamNames.isEmpty()) { s_logger.debug("Creating subscribing filter for parameters {} on method {}.{}()", new Object[]{uidParamNames, method.getDeclaringClass().getSimpleName(), method.getName()}); return new EntitySubscriptionFilter(uidParamNames, getUpdateManager(), _httpContext, _servletRequest); } else { return null; } } /** * Creates a filter that creates a subscription for a master when the method is invoked. The method must be * annotated with {@link SubscribeMaster}. A notification is sent when any data in the master changes. * @param abstractMethod A Jersey REST method * @return A filter to set up subscriptions when the method is invoked or null if the method doesn't * need master subscriptions */ private ResourceFilter createMasterSubscriptionFilter(AbstractMethod abstractMethod) { SubscribeMaster annotation = abstractMethod.getAnnotation(SubscribeMaster.class); if (annotation != null) { MasterType[] masterTypes = annotation.value(); if (masterTypes.length > 0) { return new MasterSubscriptionFilter(getUpdateManager(), Arrays.asList(masterTypes), _httpContext, _servletRequest); } else { s_logger.warn("@SubscribeMaster annotation found on {}.{}() with no masters specified", abstractMethod.getMethod().getDeclaringClass().getSimpleName(), abstractMethod.getMethod().getName()); } } return null; } }