package ameba.message.internal; import org.apache.commons.lang3.StringUtils; import org.glassfish.jersey.internal.inject.InjectionManager; import javax.annotation.Priority; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; /** * <p>StreamingWriterInterceptor class.</p> * * @author icode * */ @Singleton @Priority(Integer.MIN_VALUE + 100) public class StreamingWriterInterceptor implements WriterInterceptor, ContainerResponseFilter { private static final String ACCEPT_RANGES = "Accept-Ranges"; private static final String BYTES_RANGE = "bytes"; private static final String IF_RANGE = "If-Range"; private static final String RESP_PROP_N = StreamingWriterInterceptor.class.getName() + ".responseContext"; @Inject private Provider<ContainerRequestContext> requestProvider; @Inject private InjectionManager manager; /** * {@inheritDoc} */ @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { if (isWritable(context)) { MultivaluedMap<String, Object> respHeaders = context.getHeaders(); ContainerRequestContext requestContext = requestProvider.get(); MultivaluedMap<String, String> reqHeaders = requestContext.getHeaders(); if (reqHeaders.containsKey(MediaStreaming.RANGE)) { if (reqHeaders.containsKey(IF_RANGE)) { String ifRangeHeader = reqHeaders.getFirst(IF_RANGE); if (StringUtils.isBlank(ifRangeHeader)) { return; } if (respHeaders.containsKey(HttpHeaders.ETAG)) { if (MessageHelper.getHeaderString(respHeaders, HttpHeaders.ETAG) .equals(ifRangeHeader)) { applyStreaming(requestContext, context); return; } } if (respHeaders.containsKey(HttpHeaders.LAST_MODIFIED)) { if (MessageHelper.getHeaderString(respHeaders, HttpHeaders.LAST_MODIFIED) .equals(ifRangeHeader)) { applyStreaming(requestContext, context); } } } else { applyStreaming(requestContext, context); } } } context.proceed(); } /** * <p>isWritable.</p> * * @param context a {@link javax.ws.rs.ext.WriterInterceptorContext} object. * @return a boolean. */ protected boolean isWritable(WriterInterceptorContext context) { return context.getEntity() != null && !Boolean.FALSE.equals(requestProvider.get().getProperty(MessageHelper.STREAMING_RANGE_ENABLED)) && context.getHeaders().containsKey(HttpHeaders.CONTENT_LENGTH) && !(context.getEntity() instanceof MediaStreaming); } /** * <p>applyStreaming.</p> * * @param requestContext a {@link javax.ws.rs.container.ContainerRequestContext} object. * @param context a {@link javax.ws.rs.ext.WriterInterceptorContext} object. * @throws java.io.IOException if any. */ protected void applyStreaming(ContainerRequestContext requestContext, WriterInterceptorContext context) throws IOException { Object entity = context.getEntity(); StreamingProcess<Object> process = MessageHelper.getStreamingProcess(context.getEntity(), manager); if (process != null) { ContainerResponseContext responseContext = (ContainerResponseContext) requestContext.getProperty(RESP_PROP_N); responseContext.setStatusInfo(Response.Status.PARTIAL_CONTENT); context.getHeaders().putSingle(ACCEPT_RANGES, BYTES_RANGE); context.setType(StreamingOutput.class); context.setEntity(new MediaStreaming( entity, requestContext.getHeaderString(MediaStreaming.RANGE), process, context.getMediaType(), context.getHeaders() ) ); } } /** {@inheritDoc} */ @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { requestContext.setProperty(RESP_PROP_N, responseContext); } }