有没有办法知道在泽西@__Param fromString处理程序中解析哪个参数? [英] Is there any way to know which parameter is being parsed in a Jersey @__Param fromString handler?

查看:187
本文介绍了有没有办法知道在泽西@__Param fromString处理程序中解析哪个参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用的API已经决定接受UUID作为Base32编码的字符串,而不是标准的十六进制,破折号分隔的格式, UUID.fromString() 期望。这意味着我不能简单地写 @QueryParam UUID myUuid 作为方法参数,因为转换将失败。



我正在通过编写一个不同的 fromString 转换器的自定义对象与泽西 @QueryString @FormParam 注释。我希望能够访问 fromString 方法中的转换上下文,以便我可以提供更好的错误消息。现在,我可以做的只是如下:

  public static Base32UUID fromString(String uuidString){
final UUID uuid = UUIDUtils.fromBase32(uuidString,false);
if(null == uuid){
throw new InvalidParametersException(ImmutableList.of(Invalid uuid:+ uuidString));
}
返回新的Base32UUID(uuid);
}

我希望能够公开 参数具有无效的UUID,所以我记录的异常和返回的用户错误是非常清楚的。理想情况下,我的转换方法会有一个额外的参数,如下所示:

  public static Base32UUID fromString(
String uuidString,
String parameterName //新参数?
){
final UUID uuid = UUIDUtils.fromBase32(uuidString,false);
if(null == uuid){
throw new InvalidParametersException(ImmutableList.of(Invalid uuid:+ uuidString
+for parameter+ parameterName));
}
返回新的Base32UUID(uuid);
}

但这会打破 by-convention意味着泽西找到一个解析方法



<有一个名为 valueOf fromString的静态方法


    接受一个String参数(参见例如 Integer.valueOf(String) java.util.UUID.fromString (String));


我也看过也可以注册的ParamConverterProvider 来提供转换,但是似乎也没有添加足够的上下文。它提供的最接近的是一个Annotations数组,但是从我可以看出的注解,你不能从那里回溯到确定注释所在的变量或方法。我发现这个这个示例,但是它们没有有效地使用 Annotations [] 参数或暴露任何转换上下文我可以看到。



有没有办法获取这些信息?或者我需要在我的端点方法中回退到一个明确的转换调用?



如果它有所作为,我使用的是Dropwizard 0.8.0,它使用泽西2.16和Jetty 9.2.9.v20150224。

解决方案

所以这个可以用 ParamConverter / ParamConverterProvider 。我们只需要注入一个 ResourceInfo中 。从那里我们可以获得资源方法,只是做一些反思。以下是我已经测试过的一个示例实现,大部分工作。

  import java.lang.reflect.Type; 
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.annotation.Annotation;

import java.util.Set;
import java.util.HashSet;
import java.util.Collections;

import javax.ws.rs.FormParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.InternalServerErrorException;

@Provider
public class Base32UUIDParamConverter implements ParamConverterProvider {

@Context
private javax.inject.Provider< ResourceInfo> ResourceInfo中;

private static final Set< Class<?扩展注解>>注释;

static {
Set< Class<?扩展注解>> annots = new HashSet();
annots.add(QueryParam.class);
annots.add(FormParam.class);
ANNOTATIONS =集合。< Class<?扩展注释>> unmodifiableSet(annots);
}

@Override
public< T> ParamConverter< T> getConverter(class< T> type,
Type type1,
Annotation [] annots){

//检查是否为@FormParam或@QueryParam
for注释注释:annots){
if(!ANNOTATIONS.contains(annotation.annotationType())){
return null;
}
}

if(Base32UUID.class == type){
return new ParamConverter< T>(){

@覆盖
public T fromString(String value){
try {
Method method = resourceInfo.get()。getResourceMethod();

参数[] parameters = method.getParameters();
参数actualParam = null;

//从方法中找到实际的匹配参数。
for(参数参数:参数){
注释[] annotations = param.getAnnotations();
if(matchingAnnotationValues(annotations,annots)){
actualParam = param;
}
}

// null警告,但假设我的逻辑正确,
// null不可能。可能还是检查:-)
String paramName = actualParam.getName();
System.out.println(Param name:+ paramName);

Base32UUID uuid = new Base32UUID(value,paramName);
return type.cast(uuid);
} catch(Base32UUIDException ex){
throw new BadRequestException(ex.getMessage());
} catch(Exception ex){
throw new InternalServerErrorException(ex);
}
}

@Override
public String toString(T t){
return((Base32UUID)t).value;
}
};
}

返回null;


private boolean matchingAnnotationValues(Annotation [] annots1,
注释[] annots2)throws异常{

for(Class<?extends Annotation>注释类型:ANNOTATIONS){
if(isMatch(annots1,annots2,annotType)){
return true;
}
}
return false;
}

private< T extends Annotation> boolean isMatch(Annotation [] a1,
注释[] a2,
类< T> aType)throws Exception {
T p1 = getParamAnnotation(a1,aType);
T p2 = getParamAnnotation(a2,aType);
if(p1!= null&& p2!= null){
String value1 =(String)p1.annotationType()。getMethod(value)。invoke(p1);
String value2 =(String)p2.annotationType()。getMethod(value)。invoke(p2);
if(value1.equals(value2)){
return true;
}
}

return false;
}

private< T extends Annotation> T getParamAnnotation(Annotation [] annotations,
Class< T> paramType){
T paramAnnotation = null;
(注释注释:注释){
if(annotation.annotationType()== paramType){
paramAnnotation =(T)注释;
break;
}
}
return paramAnnotation;
}
}

有关实施的一些注意事项




  • 最重要的部分是如何注入 ResourceInfo 。由于这需要在请求范围上下文中访问,所以我注入了 javax.inject.Provider ,这允许我们懒洋洋地检索对象。当我们实际执行 get()它时,它将在请求范围内。



    要谨慎的是,它必须被调用 get() ParamConverter fromString 方法之内。 ParamConverterProvider getConverter 方法在应用程序加载期间被多次调用,因此我们无法尝试调用 get()在此期间。


  • java.lang.reflect.Parameter 我使用的类是一个Java 8类,所以为了使用这个实现,你需要在Java 8上工作。如果你不使用Java 8,这篇文章可能有助于尝试以其他方式获取参数名称。


  • 与上述相关,编译器参数需要在编译时应用 -parameters ,以便能够访问正式参数名称,如此处。我只是把它放在maven-cmpiler-plugin中,如链接中指出的那样。

     < plugin> 
    < groupId> org.apache.maven.plugins< / groupId>
    < artifactId> maven-compiler-plugin< / artifactId>
    < version> 2.5.1< / version>
    < inherited> true< / inherited>
    < configuration>
    < compilerArgument> -parameters< / compilerArgument>
    < testCompilerArgument> -parameters< / testCompilerArgument>
    < source> 1.8< / source>
    < target> 1.8< / target>
    < / configuration>
    < / plugin>

    如果不这样做,调用 Parameter.getName( )将导致 argX X 作为参数的索引。 >


  • 实现只允许 @FormParam @QueryParam


  • 需要注意的一个重要事项(我学到了硬的方式),所有在 ParamConverter (在这种情况下仅适用于@QueryParam)处理的所有异常都将导致404没有解释的问题。所以你需要确保处理你的异常,如果你想要一个不同的行为。







更新



上述实现中有一个错误:

 code> //检查是否为@FormParam或@QueryParam 
(注释注释:annots){
if(!ANNOTATIONS.contains(annotation.annotationType())){
返回null;
}
}

getConverter 被调用为每个参数。上面的代码只起作用,只有一个注释。如果除了 @QueryParam @FormParam 之外还有其他注释,说 @NotNull ,它将失败。其余的代码很好。它实际上是在假设将不止一个注释的情况下实际工作。



上述代码的修复将是类似于

  boolean hasParamAnnotation = false; 
(注释注释:annots){
if(ANNOTATIONS.contains(annotation.annotationType())){
hasParamAnnotation = true;
break;
}
}

如果(!hasParamAnnotation)返回null;


The API I'm working with has decided to accept UUIDs as Base32 encoded strings, instead of the standard hexadecimal, dash separated format that UUID.fromString() expects. This means that I can't simply write @QueryParam UUID myUuid as a method parameter, as the conversion would fail.

I'm working around this by writing a custom object with a different fromString converter to be used with the Jersey @QueryString and @FormParam annotations. I would like to be able to access the context of the conversion in the fromString method so that I can provide better error messages. Right now, all I can do is the following:

public static Base32UUID fromString(String uuidString) {
    final UUID uuid = UUIDUtils.fromBase32(uuidString, false);
    if (null == uuid) {
        throw new InvalidParametersException(ImmutableList.of("Invalid uuid: " + uuidString));
    }
    return new Base32UUID(uuid);
}

I would like to be able to expose which parameter had the invalid UUID, so my logged exceptions and returned user errors are crystal clear. Ideally, my conversion method would have an extra parameter for details, like so:

public static Base32UUID fromString(
    String uuidString,
    String parameterName // New parameter?
) {
    final UUID uuid = UUIDUtils.fromBase32(uuidString, false);
    if (null == uuid) {
        throw new InvalidParametersException(ImmutableList.of("Invalid uuid: " + uuidString
            + " for parameter " + parameterName));
    }
    return new Base32UUID(uuid);
}

But this would break the by-convention means that Jersey finds a parsing method :

  1. Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String) and java.util.UUID.fromString(String));

I've also looked at the ParamConverterProvider that can also be registered to provide conversion, but it doesn't seem to add enough context either. The closest it provides is the an array of Annotations, but from what I can tell of the annotation, you can't backtrack from there to determine which variable or method the annotation is on. I've found this and this examples, but they don't make effective use of of the Annotations[] parameter or expose any conversion context that I can see.

Is there any way to get this information? Or do I need to fallback to an explicit conversion call in my endpoint method?

If it makes a difference, I'm using Dropwizard 0.8.0, which is using Jersey 2.16 and Jetty 9.2.9.v20150224.

解决方案

So this can be accomplished with a ParamConverter/ParamConverterProvider. We just need to inject a ResourceInfo. From there we can obtain the resource Method, and just do some reflection. Below is an example implementation that I've tested and works for the most part.

import java.lang.reflect.Type;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.annotation.Annotation;

import java.util.Set;
import java.util.HashSet;
import java.util.Collections;

import javax.ws.rs.FormParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.InternalServerErrorException;

@Provider
public class Base32UUIDParamConverter implements ParamConverterProvider {

    @Context
    private javax.inject.Provider<ResourceInfo> resourceInfo;

    private static final Set<Class<? extends Annotation>> ANNOTATIONS;

    static {
        Set<Class<? extends Annotation>> annots = new HashSet<>();
        annots.add(QueryParam.class);
        annots.add(FormParam.class);
        ANNOTATIONS = Collections.<Class<? extends Annotation>>unmodifiableSet(annots);
    }

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> type, 
                                              Type type1,
                                              Annotation[] annots) {

        // Check if it is @FormParam or @QueryParam
        for (Annotation annotation : annots) {
            if (!ANNOTATIONS.contains(annotation.annotationType())) {
                return null;
            }
        }

        if (Base32UUID.class == type) {
            return new ParamConverter<T>() {

                @Override
                public T fromString(String value) {
                    try {
                        Method method = resourceInfo.get().getResourceMethod();

                        Parameter[] parameters = method.getParameters();
                        Parameter actualParam = null;

                        // Find the actual matching parameter from the method.
                        for (Parameter param : parameters) {
                            Annotation[] annotations = param.getAnnotations();
                            if (matchingAnnotationValues(annotations, annots)) {
                                actualParam = param;
                            }
                        }

                        // null warning, but assuming my logic is correct, 
                        // null shouldn't be possible. Maybe check anyway :-)
                        String paramName = actualParam.getName();
                        System.out.println("Param name : " + paramName);

                        Base32UUID uuid = new Base32UUID(value, paramName);
                        return type.cast(uuid);
                    } catch (Base32UUIDException ex) {
                        throw new BadRequestException(ex.getMessage());
                    } catch (Exception ex) {
                        throw new InternalServerErrorException(ex);
                    }
                }

                @Override
                public String toString(T t) {
                    return ((Base32UUID) t).value;
                }
            };
        }

        return null;
    }

    private boolean matchingAnnotationValues(Annotation[] annots1, 
                                             Annotation[] annots2) throws Exception {

        for (Class<? extends Annotation> annotType : ANNOTATIONS) {
            if (isMatch(annots1, annots2, annotType)) {
                return true;
            }
        }
        return false;
    }

    private <T extends Annotation> boolean isMatch(Annotation[] a1, 
                                                   Annotation[] a2, 
                                                   Class<T> aType) throws Exception {
        T p1 = getParamAnnotation(a1, aType);
        T p2 = getParamAnnotation(a2, aType);
        if (p1 != null && p2 != null) {
            String value1 = (String) p1.annotationType().getMethod("value").invoke(p1);
            String value2 = (String) p2.annotationType().getMethod("value").invoke(p2);
            if (value1.equals(value2)) {
                return true;
            }
        }

        return false;
    }

    private <T extends Annotation> T getParamAnnotation(Annotation[] annotations, 
                                                        Class<T> paramType) {
        T paramAnnotation = null;
        for (Annotation annotation : annotations) {
            if (annotation.annotationType() == paramType) {
                paramAnnotation = (T) annotation;
                break;
            }
        }
        return paramAnnotation;
    }
}

Some notes about the implementation

  • The most important part is how the ResourceInfo is injected. Since this needs to be accessed in a request scope context, I injected with javax.inject.Provider, which allows us to retrieve the object lazily. When we actually do get() it, it will be within a request scope.

    The thing to be cautious about is that it get() must be called inside the fromString method of the ParamConverter. The getConverter method of the ParamConverterProvider is called many times during application load, so we cannot try and call the get() during this time.

  • The java.lang.reflect.Parameter class I used is a Java 8 class, so in order to use this implementation, you need to be working on Java 8. If you are not using Java 8, this post may help in trying to get the parameter name some other way.

  • Related to the above point, the compiler argument -parameters needs to be applied when compiling, to be able to access the formal parameter name, as pointed out here. I just put it in the maven-cmpiler-plugin as pointed out in the link.

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.5.1</version>
        <inherited>true</inherited>
        <configuration>
            <compilerArgument>-parameters</compilerArgument>
            <testCompilerArgument>-parameters</testCompilerArgument>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
    

    If you don't do this, a call to Parameter.getName() will result in argX, X being the index of the parameter.

  • The implementation only allows for @FormParam and @QueryParam.

  • One important thing to note (that I learned the hard way), is that all exceptions that aren't handle in the ParamConverter (only applies to @QueryParam in this case), will lead to a 404 with no explanation of the problem. So you you need to make sure you handle your exception if you want a different behavior.


UPDATE

There is a bug in the above implementation:

// Check if it is @FormParam or @QueryParam
for (Annotation annotation : annots) {
    if (!ANNOTATIONS.contains(annotation.annotationType())) {
        return null;
    }
}

The above is called during model validation when getConverter is called for each parameter. The above code only works is there is only one annotation. If there is another annotation aside from @QueryParam or @FormParam, say @NotNull, it will fail. The rest of the code is fine. It does actually work under the assumption that there will be more than one annotation.

The fix to the above code, would be something like

boolean hasParamAnnotation = false;
for (Annotation annotation : annots) {
    if (ANNOTATIONS.contains(annotation.annotationType())) {
        hasParamAnnotation = true;
        break;
    }
}

if (!hasParamAnnotation) return null;

这篇关于有没有办法知道在泽西@__Param fromString处理程序中解析哪个参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆