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

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

问题描述

我和决定工作API接受的UUID为Base32 EN codeD字符串,而不是标准的十六进制,短线分隔格式,<一个href=\"http://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#fromString-java.lang.String-\"><$c$c>UUID.fromString()预计。这意味着,我不能简单地写 @QueryParam UUID myUuid 作为方法参数,作为转换会失败。

我解决这个工作通过编写自定义对象使用不同的 fromString 转换器与泽西 @QueryString @FormParam 注释。我希望能够访问转换的背景下在 fromString 方法,这样我可以提供更好的错误消息。现在,我所能做的是以下内容:

 公共静态Base32UUID fromString(字符串uuidString){
    最终UUID UUID = UUIDUtils.fromBase32(uuidString,FALSE);
    如果(空== UUID){
        抛出新InvalidParametersException(ImmutableList.of(无效的UUID:+ uuidString));
    }
    返回新Base32UUID(UUID);
}

我希望能够揭露的的参数有无效的UUID,所以我的记录异常和返回的用户错误都一清二楚。理想情况下,我的转换方法将对细节的额外的参数,像这样:

 公共静态Base32UUID fromString(
    串uuidString,
    字符串参数名称//新参数?
){
    最终UUID UUID = UUIDUtils.fromBase32(uuidString,FALSE);
    如果(空== UUID){
        抛出新InvalidParametersException(ImmutableList.of(无效的UUID:+ uuidString
            +参数+参数名称));
    }
    返回新Base32UUID(UUID);
}

但是,通过会展意味着新泽西州发现了一个分析方法这将打破


  <醇开始=3>
  
  • 有一个名为静态方法的valueOf fromString 接受一个字符串参数(见,例如, Integer.valueOf(字符串) java.util.UUID.fromString(字符串));

  •   

    我也看了 ParamConverterProvider 这也可以注册提供的转换,但它似乎并不要么补充足够的上下文。它提供了最接近的是注解的一个数组,但我可以告诉注释的,你不能从那里原路返回,以确定注释是哪个变量或方法。我发现<一个href=\"https://github.com/javaee-samples/javaee7-samples/blob/master/jaxrs/paramconverter/src/main/java/org/javaee7/jaxrs/paramconverter/MyBeanConverterProvider.java\">this和<一个href=\"http://blog.dejavu.sk/2014/02/11/inject-custom-java-types-via-jax-rs-parameter-annotations/\">this的例子,但他们没有有效地利用的注解[] 参数或暴露,我可以看到任何转换环境。

    有没有什么办法让这一信息?或者,我需要退回到显式转换呼叫我的终点的方法?

    如果它的确与众不同,我使用Dropwizard 0.8.0,这是使用泽西2.16和Jetty 9.2.9.v20150224。


    解决方案

    所以这个的可以的与一个的 ParamConverter /的 ParamConverterProvider 。我们只需要注入 ResourceInfo中 。从这里我们可以得到资源方法,只是做了一些反思。下面是我测试过,并适用于大部分的示例实现。

     进口java.lang.reflect.Type;
    进口的java.lang.reflect.Method;
    进口java.lang.reflect.Parameter;
    进口java.lang.annotation.Annotation;进口java.util.Set中;
    进口java.util.HashSet中;
    进口java.util.Collections中;进口javax.ws.rs.FormParam;
    进口javax.ws.rs.QueryParam;
    进口javax.ws.rs.core.Context;
    进口javax.ws.rs.ext.Provider;
    进口javax.ws.rs.container.ResourceInfo;
    进口javax.ws.rs.ext.ParamConverter;
    进口javax.ws.rs.ext.ParamConverterProvider;
    进口javax.ws.rs.BadRequestException;
    进口javax.ws.rs.InternalServerErrorException;@Provider
    公共类Base32UUIDParamConverter实现ParamConverterProvider {    @Context
        私人javax.inject.Provider&LT;&ResourceInfo中GT; ResourceInfo中;    私有静态决胜盘&LT;&级LT ;?扩展注解&GT;&GT;注释;    静态的 {
            SET&LT;&级LT ;?扩展注解&GT;&GT; annots =新的HashSet&LT;&GT;();
            annots.add(QueryParam.class);
            annots.add(FormParam.class);
            说明一=收藏与LT;类&LT ;?扩展注解&GT;&GT; unmodifiableSet(annots);
        }    @覆盖
        公众&LT; T&GT; ParamConverter&LT; T&GT; getConverter(类&LT; T&GT;类型,
                                                  键入TYPE1,
                                                  注释[] annots){        //检查是否是@FormParam或@QueryParam
            对于(译注注解:annots){
                如果(!ANNOTATIONS.contains(annotation.annotationType())){
                    返回null;
                }
            }        如果(Base32UUID.class ==型){
                返回新ParamConverter&LT; T&GT;(){                @覆盖
                    公共ŧfromString(字符串值){
                        尝试{
                            方法方法= resourceInfo.get()getResourceMethod()。                        参数[] =参数method.getParameters();
                            参数actualParam = NULL;                        //查找该方法的实际匹配参数。
                            对于(参数参数:参数){
                                注释[] =注释param.getAnnotations();
                                如果(matchingAnnotationValues​​(注释,annots)){
                                    actualParam =参数;
                                }
                            }                        //空的警告,但假设我的逻辑是正确的,
                            //空不应该是可能的。或许检查反正:-)
                            串paramName配置= actualParam.getName();
                            的System.out.println(参数名:+ paramName配置);                        Base32UUID UUID =新Base32UUID(值,paramName配置);
                            返回type.cast(UUID);
                        }赶上(Base32UUIDException前){
                            抛出新BadRequestException(ex.getMessage());
                        }赶上(例外前){
                            抛出新InternalServerErrorException(除息);
                        }
                    }                @覆盖
                    公共字符串的toString(T(T)){
                        回报((Base32UUID)T).value的;
                    }
                };
            }        返回null;
        }    私人布尔matchingAnnotationValues​​(译注[] annots1,
                                                 注释[] annots2)抛出异常{        对于(类&LT ;?扩展注解&GT; annotType:注释){
                如果(isMatch(annots1,annots2,annotType)){
                    返回true;
                }
            }
            返回false;
        }    私人&LT;吨延伸注解&GT;布尔isMatch(译注[] A1,
                                                       注释[] A2,
                                                       类&LT; T&GT; ATYPE)抛出异常{
            Tp 1 = getParamAnnotation(A1,atype的);
            ŧP2 = getParamAnnotation(A2,atype的);
            如果(P1 = NULL&放大器;!&安培;!P2 = NULL){
                字符串值1 =(字符串)p1.annotationType()实现getMethod(值)调用(P1)。
                字符串值2 =(字符串)p2.annotationType()实现getMethod(值)调用(P2)。
                如果(value1.equals(值2)){
                    返回true;
                }
            }        返回false;
        }    私人&LT;吨延伸注解&GT; ŧgetParamAnnotation(译注[]注释,
                                                            类&LT; T&GT; paramType){
            ŧparamAnnotation = NULL;
            对于(译注注解:注解){
                如果(annotation.annotationType()== paramType){
                    paramAnnotation =(T)注释;
                    打破;
                }
            }
            返回paramAnnotation;
        }
    }

    有关实施的一些注意事项


    • 中最重要的部分是如何使用 ResourceInfo中注入。由于这需要在一个请求范围上下文进行访问,我注射了 javax.inject.Provider ,这使我们能够懒洋洋地检索对象。当我们实际上做的get()它,这将是一个要求范围之内。

      要谨慎对待的事情是,它的get()必须的调用中的 fromString ParamConverter 方法。的 getConverter 方法 ParamConverterProvider 在应用程序的负载被称为很多次,所以我们不能尝试调用<$ C在此期间,$ C>获得()


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


    • 有关上述点,编译器参数α参数进行编译的时候,要能够访问形式参数的名称指出要应用, 这里。我只是把它在Maven的cmpiler-插件为链接指出。

       &LT;&插件GT;
          &LT;&的groupId GT; org.apache.maven.plugins&LT; /的groupId&GT;
          &LT;&的artifactId GT; Maven的编译器插件&LT; / artifactId的&GT;
          &LT;&版GT; 2.5.1&LT; /版本&GT;
          &LT;&继承GT;真&LT; /继承&GT;
          &LT;结构&gt;
              &LT; compilerArgument&GT;α参数&LT; / compilerArgument&GT;
              &LT; testCompilerArgument&GT;α参数&LT; / testCompilerArgument&GT;
              &lt;信源&GT; 1.8 LT; /源&GT;
              &lt;目标&GT; 1.8 LT; /目标与GT;
          &LT; /结构&gt;
      &LT; /插件&GT;

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


    • 的实施只允许 @FormParam @QueryParam


    • 一个重要的一点要注意(,我学到了艰辛的道路),是所有例外AREN 'T处理的 ParamConverter (仅适用于@QueryParam在这种情况下),将导致404没有问题的解释。所以,你需要确保,如果你想要一个不同的行为,你处理你的例外。



    更新

    有在上述实现中的错误:

      //检查是否是@FormParam或@QueryParam
    对于(译注注解:annots){
        如果(!ANNOTATIONS.contains(annotation.annotationType())){
            返回null;
        }
    }

    上面的模型验证过程中被调用时 getConverter 被称为每个参数。以上code只能是只有一个注解。如果有另一个标注除了 @QueryParam @FormParam ,说 @NotNull ,它就会失败。在code的其余部分是好的。它实际上假定存在的将会的一个以上的注解下工作。

    的修复上述code,会像

     布尔hasParamAnnotation = FALSE;
    对于(译注注解:annots){
        如果(ANNOTATIONS.contains(annotation.annotationType())){
            hasParamAnnotation = TRUE;
            打破;
        }
    }如果(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天全站免登陆