package ameba.core.ws.rs; import ameba.core.ServiceLocators; import jersey.repackaged.com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.model.internal.RankedComparator; import org.glassfish.jersey.model.internal.RankedProvider; import org.glassfish.jersey.process.Inflector; import org.glassfish.jersey.server.ExtendedUriInfo; import org.glassfish.jersey.server.model.ModelProcessor; import org.glassfish.jersey.server.model.ResourceModel; import org.glassfish.jersey.server.model.internal.ModelProcessorUtil; import javax.annotation.Priority; import javax.inject.Inject; import javax.inject.Singleton; import javax.ws.rs.HttpMethod; import javax.ws.rs.Priorities; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.*; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.List; import java.util.Set; import static ameba.message.internal.MediaType.WILDCARD_TYPE; import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; /** * <p>OptionsMethodProcessor class.</p> * * @author icode * @since 0.1.6e * */ @Priority(Integer.MAX_VALUE) @Singleton public class OptionsMethodProcessor implements ModelProcessor { private static String SUPPORT_PATCH_MEDIA_TYPES = null; private static Iterable<OptionsResponseGenerator> generators; private final List<ModelProcessorUtil.Method> methodList; /** * Creates new instance. * * @param locator a {@link org.glassfish.hk2.api.ServiceLocator} object. */ @Inject public OptionsMethodProcessor(ServiceLocator locator) { methodList = Lists.newArrayList(); methodList.add(new ModelProcessorUtil.Method(HttpMethod.OPTIONS, WILDCARD_TYPE, WILDCARD_TYPE, GenericOptionsInflector.class)); final Iterable<RankedProvider<OptionsResponseGenerator>> rankedProviders = ServiceLocators.getRankedProviders(locator, OptionsResponseGenerator.class); generators = ServiceLocators .sortRankedProviders(new RankedComparator<>(), rankedProviders); } /** * <p>getSupportPatchMediaTypes.</p> * * @return a {@link java.lang.String} object. */ public static String getSupportPatchMediaTypes() { if (SUPPORT_PATCH_MEDIA_TYPES == null) { synchronized (HttpPatchProperties.SUPPORT_PATCH_MEDIA_TYPES) { if (SUPPORT_PATCH_MEDIA_TYPES == null) { SUPPORT_PATCH_MEDIA_TYPES = StringUtils.join(HttpPatchProperties.SUPPORT_PATCH_MEDIA_TYPES, ","); } } } return SUPPORT_PATCH_MEDIA_TYPES; } /** * <p>getSupportProduceMediaType.</p> * * @param containerRequestContext a {@link javax.ws.rs.container.ContainerRequestContext} object. * @return a {@link javax.ws.rs.core.MediaType} object. */ protected static MediaType getSupportProduceMediaType(ContainerRequestContext containerRequestContext) { for (MediaType mediaType : containerRequestContext.getAcceptableMediaTypes()) { if (mediaType.isCompatible(TEXT_PLAIN_TYPE) || (mediaType.getType().equalsIgnoreCase("application") && !mediaType.getSubtype().toLowerCase().contains("html")) || mediaType.getSubtype().equalsIgnoreCase("json") || mediaType.getSubtype().equalsIgnoreCase("xml") || mediaType.getSubtype().toLowerCase().endsWith("+json") || mediaType.getSubtype().toLowerCase().endsWith("+xml")) { return mediaType; } } return null; } /** * <p>generateRespBuilder.</p> * * @param containerRequestContext a {@link javax.ws.rs.container.ContainerRequestContext} object. * @param extendedUriInfo a {@link org.glassfish.jersey.server.ExtendedUriInfo} object. * @param mediaType a {@link javax.ws.rs.core.MediaType} object. * @param respEntityGenerators a {@link java.lang.Iterable} object. * @return a {@link javax.ws.rs.core.Response.ResponseBuilder} object. */ protected static Response.ResponseBuilder generateRespBuilder( ContainerRequestContext containerRequestContext, ExtendedUriInfo extendedUriInfo, MediaType mediaType, Iterable<OptionsResponseGenerator> respEntityGenerators) { final Set<String> allowedMethods = ModelProcessorUtil.getAllowedMethods( (extendedUriInfo.getMatchedRuntimeResources().get(0))); Response.ResponseBuilder builder = Response.ok().allow(allowedMethods); if (allowedMethods.contains(HttpPatchProperties.METHOD_NAME)) { builder.header(HttpPatchProperties.ACCEPT_PATCH_HEADER, getSupportPatchMediaTypes()); } if (mediaType != null) { builder.type(mediaType); } if (respEntityGenerators != null) { Response response = builder.build(); for (OptionsResponseGenerator generator : respEntityGenerators) { response = generator.generate(allowedMethods, mediaType, extendedUriInfo, containerRequestContext, response); } builder = Response.fromResponse(response); } return builder; } /** * <p>generateRespBuilder.</p> * * @param extendedUriInfo a {@link org.glassfish.jersey.server.ExtendedUriInfo} object. * @param mediaType a {@link javax.ws.rs.core.MediaType} object. * @return a {@link javax.ws.rs.core.Response.ResponseBuilder} object. */ protected static Response.ResponseBuilder generateRespBuilder(ExtendedUriInfo extendedUriInfo, MediaType mediaType) { return generateRespBuilder(null, extendedUriInfo, mediaType, null); } /** * {@inheritDoc} */ @Override public ResourceModel processResourceModel(ResourceModel resourceModel, Configuration configuration) { return ModelProcessorUtil.enhanceResourceModel(resourceModel, false, methodList, true).build(); } /** {@inheritDoc} */ @Override public ResourceModel processSubResource(ResourceModel subResourceModel, Configuration configuration) { return ModelProcessorUtil.enhanceResourceModel(subResourceModel, true, methodList, true).build(); } @XmlRootElement protected static class AllowedMethods implements Serializable { protected Set<String> allow; protected Set<AllowedMethods> children; protected String uri; public AllowedMethods(String uri, Set<String> allow) { this.allow = allow; this.uri = uri; } public AllowedMethods(String uri, Set<String> allow, Set<AllowedMethods> children) { this.allow = allow; this.children = children; this.uri = uri; } public Set<AllowedMethods> getChildren() { return children; } public String getUri() { return uri; } public Set<String> getAllow() { return allow; } } protected static class GenericOptionsInflector implements Inflector<ContainerRequestContext, Response> { @Context private ExtendedUriInfo extendedUriInfo; @Override public Response apply(ContainerRequestContext containerRequestContext) { final MediaType foundMediaType = getSupportProduceMediaType(containerRequestContext); if (foundMediaType != null) { return generateRespBuilder(containerRequestContext, extendedUriInfo, foundMediaType, generators).build(); } return generateRespBuilder(extendedUriInfo, containerRequestContext.getAcceptableMediaTypes().get(0)) .header(HttpHeaders.CONTENT_LENGTH, 0) .build(); } } @Priority(Priorities.HEADER_DECORATOR) static class DefaultOptionsResponseGenerator implements OptionsResponseGenerator { @Override public Response generate(Set<String> allowedMethods, MediaType mediaType, ExtendedUriInfo extendedUriInfo, ContainerRequestContext containerRequestContext, Response response) { Response.ResponseBuilder builder = Response.fromResponse(response); if (mediaType.isCompatible(TEXT_PLAIN_TYPE)) { return builder.entity(StringUtils.join(allowedMethods, ",")).build(); } String uri = extendedUriInfo.getMatchedModelResource().getPathPattern().getTemplate().getTemplate(); return builder.entity(new AllowedMethods(uri, allowedMethods)).build(); } } }