如何从请求上下文中获取URL匹配模板

时间:2016-03-24 18:47:21

标签: java jax-rs jersey-2.0

有jersey-2动态构建端点RESTful资源。 路径方法

resourceBuilder.path("item/{id}");

为特定资源定义匹配的url模式。例如。当请求www.myapp.com/api/item/22资源时,具有上述路径模式的处理程序将处理请求。

我需要找出请求呼叫将匹配的路径模式(此处为' s" item / {id}")。

getMatchedResources()或getMatchedURIs()不提供路径模式列表。

methodBuilder.produces(new MediaType("text", "plain"))
.handledBy(new Inflector<ContainerRequestContext, String>() {

    @Override
    public String apply(ContainerRequestContext ctx) {

        List<Object> resources = ctx.getUriInfo().getMatchedResources();
        List<String> uris = ctx.getUriInfo().getMatchedURIs();

        return "Programmatically generated endpoint";
    }
});

有没有人解决类似的问题?

2 个答案:

答案 0 :(得分:1)

由于您动态生成端点,因此可以通过多种方式执行此操作。这是实现同样目标的两种方法。

第一种方法:

创建自定义Inflector:

    // Special type of Inflector that's aware of the resource path. Users extending this class implement the
    // apply(data, resourcePath) method. Internally it uses a custom resource registry to get the resource path.
    public static abstract class PathAwareInflector<RESULT> implements Inflector<ContainerRequestContext, RESULT> {

        @Inject
        private ResourceRegistry resourceRegistry;
        private String resourcePath;

        @Override
        public final RESULT apply(ContainerRequestContext data) {
            return apply(data, getResourcePath(data));
        }

        public abstract RESULT apply(ContainerRequestContext data, String resourcePath);

        private String getResourcePath(ContainerRequestContext data) {
            if (resourcePath == null) {
                resourcePath = resourceRegistry.forPath(data.getUriInfo().getPath(true))
                    .stream()
                    .findFirst()
                    .map(Resource::getPath)
                    .orElse("UNKNOWN");
            }
            return resourcePath;
        }
    }

然后您可以注册您的资源:

    final Resource.Builder resourceBuilder = Resource.builder("/item/{id}");
    final ResourceMethod.Builder methodBuilder = resourceBuilder.addMethod("GET");
    methodBuilder.produces(MediaType.TEXT_PLAIN_TYPE)
        .handledBy(new PathAwareInflector<String>() {

            @Override
            public String apply(ContainerRequestContext context, String resourcePath) {
                return resourcePath + "\n";
            }
        });

    registerResources(resourceBuilder.build());

第二种方法

使用自定义注释@ResourcePath,您可以像这样定义方法:

// Simple Endpoint with one method. Uses special custom @ResourcePath annotation to inject the resource path in two ways:
// As method argument, and as field using the same annotation. In a real world scenario you'd user either or.
public static class Endpoint {

    @ResourcePath
    String resourcePath2;

    public String endpoint(ContainerRequestContext context, @ResourcePath String resourcePath) {
        return resourcePath2 + " - " + resourcePath + "\n";
    }
}

你注册就是这样:

    // This resource is configured through a class method. While the path, method, and produces are defined here,
    // the Endpoint.endpoint method actually handles the resource, which class and parameters can be annotated.
    final Resource.Builder resourceBuilder1 = Resource.builder("/annotation/{id}");
    final ResourceMethod.Builder methodBuilder1 = resourceBuilder1.addMethod("GET");
    methodBuilder1.produces(MediaType.TEXT_PLAIN_TYPE)
        .handledBy(Endpoint.class, Endpoint.class.getMethod("endpoint", ContainerRequestContext.class, String.class));

    registerResources(resourceBuilder1.build());

要支持这种机制,您还需要有这些类:

// Custom annotation used by ResourcePathInjectionResolver and ResourcePathValueFactoryProvider to
// inject field and parameters annotated with this annotation.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ResourcePath {

}


// It registers providers/injection resolvers/factories to enable @ResourcePath and PathAwareInflector
public static class PathResourceFeature implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        context.register(new ResourceRegistry.Binder());
        context.register(ResourceRegistry.Processor.class);
        context.register(new ResourcePathInjectionResolver.Binder());
        context.register(ResourcePathValueFactoryProvider.class);
        return true;
    }
}

// Custom injector that resolves @ResourcePath annotations for field injection. It delegates to
// ResourcePathValueFactoryProvider extending a ParamInjectionResolver.
public static class ResourcePathInjectionResolver extends ParamInjectionResolver<ResourcePath> {

    static final class Binder extends AbstractBinder {

        @Override
        protected void configure() {
            bind(ResourcePathInjectionResolver.class)
                .to(new TypeLiteral<InjectionResolver<ResourcePath>>() {
                })
                .in(Singleton.class);
        }
    }

    public ResourcePathInjectionResolver() {
        super(ResourcePathValueFactoryProvider.class);
    }
}

// Custom Factory Provider that resolves @ResourcePath annotations for parameter injection. It
// uses a custom Resource Registry to find the actual path.
public static class ResourcePathValueFactoryProvider extends AbstractValueFactoryProvider {

    public static class ResourcePathValueFactory extends AbstractContainerRequestValueFactory<String> {

        @Inject
        private ResourceRegistry resourceRegistry;

        public ResourcePathValueFactory() {
        }

        @Override
        public String provide() {
            return resourceRegistry.forPath(getContainerRequest().getPath(true))
                .stream()
                .findFirst()
                .map(Resource::getPath)
                .orElse(null);
        }
    }

    @Inject
    public ResourcePathValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
        super(mpep, locator, Parameter.Source.values());
    }

    @Override
    protected Factory<?> createValueFactory(Parameter parameter) {
        if (parameter.isAnnotationPresent(ResourcePath.class)) {
            return new ResourcePathValueFactory();
        }
        return null;
    }
}

// Resource registry that stores the Resource Model and its configuration. It is bootstrapped with custom ModelProcessor
// that simply stores the resource model into the registry.
public static class ResourceRegistry {

    private ResourceModel resourceModel;

    private void setResourceModel(ResourceModel resourceModel) {
        this.resourceModel = resourceModel;
    }

    protected List<Resource> forPath(String path) {
        return resourceModel.getResources()
            .stream()
            .filter(r -> r.getPathPattern().match("/" + path) != null)
            .collect(Collectors.toList());
    }

    static final class Binder extends AbstractBinder {
        @Override
        protected void configure() {
            bind(ResourceRegistry.class)
                .to(ResourceRegistry.class)
                .in(Singleton.class);
        }
    }

    public static class Processor implements ModelProcessor {

        private ResourceRegistry resourceRegistry;

        @Inject
        public Processor(ResourceRegistry resourceRegistry) {
            this.resourceRegistry = resourceRegistry;
        }

        @Override
        public ResourceModel processResourceModel(ResourceModel resourceModel, Configuration configuration) {
            resourceModel.getResources().stream().forEach(r -> {
                System.out.println("Path: " + r.getPath());
                System.out.println("Regex: " + r.getPathPattern().getRegex());
            });
            resourceRegistry.setResourceModel(resourceModel);
            return resourceModel;
        }

        @Override
        public ResourceModel processSubResource(ResourceModel subResourceModel, Configuration configuration) {
            return subResourceModel;
        }
    }
}

在您的配置中注册功能:

    //Registers the Path Resource Feature that enables the functionality.
    register(PathResourceFeature.class);

答案 1 :(得分:1)

我能够从请求的上下文中检索匹配的模板,方法是将其转换为ContainerRequest,然后调用getMatchedTemplates()方法。

methodBuilder.produces(new MediaType("text", "plain"))
    .handledBy(new Inflector<ContainerRequestContext, String>() {

    @Override
    public String apply(ContainerRequestContext ctx) {

        List<UriTemplate> uriTemplates =  ((ContainerRequest) requestContext).getUriInfo().getMatchedTemplates();
        if(uriTemplates != null && uriTemplates.size() > 0)
        {
            UriTemplate uriTemplate = uriTemplates.get(0);
            String pathTemplate = uriTemplate.getTemplate();

            //pathTemplate == "/item/{id}"
        }

        return "Programmatically generated endpoint";
   }
});