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

问题描述

我和决定工作API接受的UUID为Base32 EN codeD字符串,而不是标准的十六进制,短线分隔格式,<$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);
}

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

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处理程序被解析?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-29 10:44
查看更多