2015-12-30 1 views
5

Возможно ли разрешить несколько@QueryParam ключей для одного объекта/переменной в Джерси?Несколько ключей @QueryParam для одного значения в Джерси

enter image description here

Actual:

@POST 
public Something getThings(@QueryParam("customer-number") Integer n) { 
    ... 
} 

так, если я добавлю ?customer-number=3 после URL она работает.

Ожидаемое:

Я хочу, чтобы получить поведение выше, если добавить какой-либо из следующих значений:

  • ?customer-number=3
  • ?customerNumber=3
  • ?customerNo=3

Obs:

  1. QueryParam аннотацию выглядит следующим образом:

    ... 
    public @interface QueryParam { 
        String value(); 
    } 
    

    так, он не может принимать несколько значений String (как @Produces).

  2. подход ниже, позволяет пользователю использовать несколько ключей, имеющих тот же смысл, в то же время (и я хочу иметь «ИЛИ» состояние между ними):

    @POST 
    public Something getThings(@QueryParam("customer-number") Integer n1, 
              @QueryParam("customerNumber") Integer n2, 
              @QueryParam("customerNo") Integer n3) { 
        ... 
    } 
    
  3. Что-то вроде этого Безразлично «т работы:

    @POST 
    public Something getThings(@QueryParam("customer-number|customerNumber|customerNo") Integer n) { 
        ... 
    } 
    

Как я могу это сделать?

Детали:

  • Джерси 2,22.1
  • Java 8

ответ

1

Может быть, самый простой и легкий способ будет использовать пользовательские @BeanParam:

сначала определить пользовательский компонент переходящую все параметры запроса, как:

class MergedIntegerValue { 

    private final Integer value; 

    public MergedIntegerValue(
     @QueryParam("n1") Integer n1, 
     @QueryParam("n2") Integer n2, 
     @QueryParam("n3") Integer n3) { 
    this.value = n1 != null ? n1 
     : n2 != null ? n2 
     : n3 != null ? n3 
     : null; 
    // Throw an exception if value == null ? 
    } 

    public Integer getValue() { 
    return value; 
    } 
} 

и затем используйте его с @BeanParam в вашем ресурсном режиме:

public Something getThings(
    @BeanParam MergedIntegerValue n) { 

    // Use n.getValue() ... 
} 

Код: https://jersey.java.net/documentation/latest/user-guide.html#d0e2403

1

Если честно: это не значит, что веб-сервисы должны быть спроектированы. Вы устанавливаете строгий контракт, которым следуют клиент и сервер; вы определяете один параметр и все.

Но, конечно, это был бы идеальный мир, где у вас есть свобода диктовать, что произойдет. Поэтому, если вы должны разрешить три параметра, тогда вам придется заключить этот контракт. Это один из способов следующего подход # 2, который я должен обеспечить, не имея возможности проверить его на нестыковки:

public Something getThings(@QueryParam("customer-number") Integer n1, 
          @QueryParam("customerNumber") Integer n2, 
          @QueryParam("customerNo") Integer n3) throws YourFailureException { 

    Integer customerNumber = getNonNullValue("Customer number", n1, n2, n3); 
    // things with stuff 
} 

private static Integer getNonNullValue(String label, Integer... params) throws YourFailureException { 

    Integer value = null; 

    for(Integer choice : params){ 
    if(choice != null){ 
     if(value != null){ 
     // this means there are at least two query parameters passed with a value 
     throw new YourFailureException("Ambiguous " + label + " parameters"); 
     } 

     value = choice; 
    } 
    } 

    if(value == null){ 
    throw new YourFailureException("Missing " + label + " parameter"); 
    } 

    return value; 
} 

Поэтому в основном отвергает любой вызов, который не проходит конкретно один из параметров, и Пусть картограф исключения перевода исключение, которое вы бросаете в код ответа HTTP в курсе 4xx.

(Я сделал метод getNonNullValue() static, это ставит меня как функцию многократного использования).

1

Вы можете создать пользовательскую аннотацию. Я не буду слишком много рассказывать о том, как это сделать, вы можете увидеть this post или this post. В основном он опирается на другую инфраструктуру, чем обычная инъекция зависимостей с Джерси. Вы можете увидеть this package из проекта Джерси. Именно там живут все поставщики инъекций, которые обрабатывают инъекции @XxxParam. Если вы изучите исходный код, вы увидите, что реализации являются одинаковыми. Две приведенные выше ссылки соответствуют одному и тому же шаблону, а также следующему коду.

То, что я был создан пользовательский аннотацию

@Target({ElementType.FIELD, ElementType.PARAMETER}) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface VaryingParam { 

    String value(); 

    @SuppressWarnings("AnnotationAsSuperInterface") 
    public static class Factory 
      extends AnnotationLiteral<VaryingParam> implements VaryingParam { 

     private final String value; 

     public static VaryingParam create(final String newValue) { 
      return new Factory(newValue); 
     } 

     public Factory(String newValue) { 
      this.value = newValue; 
     } 

     @Override 
     public String value() { 
      return this.value; 
     } 
    } 
} 

Это может показаться странным, что у меня есть завод, чтобы создать его, но это было необходимо для реализации кода ниже, где я расколоть значение String, и в конечном итоге создайте новый экземпляр аннотации для каждого значения split.

Вот ValueFactoryProvider (который, если вы прочитали одну из приведенных выше статей, вы увидите, что это необходимо для инкрементной настройки параметров метода). Это большой класс, только потому, что я поместил все необходимые классы в один класс, следуя шаблону, который вы видите в проекте Джерси.

public class VaryingParamValueFactoryProvider extends AbstractValueFactoryProvider { 

    @Inject 
    public VaryingParamValueFactoryProvider(
      final MultivaluedParameterExtractorProvider mpep, 
      final ServiceLocator locator) { 
     super(mpep, locator, Parameter.Source.UNKNOWN); 
    } 

    @Override 
    protected Factory<?> createValueFactory(final Parameter parameter) { 
     VaryingParam annotation = parameter.getAnnotation(VaryingParam.class); 
     if (annotation == null) { 
      return null; 
     } 
     String value = annotation.value(); 
     if (value == null || value.length() == 0) { 
      return null; 
     } 
     String[] variations = value.split("\\s*\\|\\s*"); 
     return new VaryingParamFactory(variations, parameter); 
    } 

    private static Parameter cloneParameter(final Parameter original, final String value) { 
     Annotation[] annotations = changeVaryingParam(original.getAnnotations(), value); 
     Parameter clone = Parameter.create(
       original.getRawType(), 
       original.getRawType(), 
       true, 
       original.getRawType(), 
       original.getRawType(), 
       annotations); 
     return clone; 
    } 

    private static Annotation[] changeVaryingParam(final Annotation[] annos, final String value) { 
     for (int i = 0; i < annos.length; i++) { 
      if (annos[i] instanceof VaryingParam) { 
       annos[i] = VaryingParam.Factory.create(value); 
       break; 
      } 
     } 
     return annos; 
    } 

    private class VaryingParamFactory extends AbstractContainerRequestValueFactory<Object> { 

     private final String[] variations; 
     private final Parameter parameter; 
     private final boolean decode; 
     private final Class<?> paramType; 
     private final boolean isList; 
     private final boolean isSet; 

     VaryingParamFactory(final String[] variations, final Parameter parameter) { 
      this.variations = variations; 
      this.parameter = parameter; 
      this.decode = !parameter.isEncoded(); 
      this.paramType = parameter.getRawType(); 
      this.isList = paramType == List.class; 
      this.isSet = paramType == Set.class; 
     } 

     @Override 
     public Object provide() { 
      MultivaluedParameterExtractor<?> e = null; 
      try { 
       Object value = null; 
       MultivaluedMap<String, String> params 
         = getContainerRequest().getUriInfo().getQueryParameters(decode); 
       for (String variant : variations) { 
        e = get(cloneParameter(parameter, variant)); 
        if (e == null) { 
         return null; 
        } 
        if (isList) { 
         List list = (List<?>) e.extract(params); 
         if (value == null) { 
          value = new ArrayList(); 
         } 
         ((List<?>) value).addAll(list); 
        } else if (isSet) { 
         Set set = (Set<?>) e.extract(params); 
         if (value == null) { 
          value = new HashSet(); 
         } 
         ((Set<?>) value).addAll(set); 
        } else { 
         value = e.extract(params); 
         if (value != null) { 
          return value; 
         }   
        } 
       } 
       return value; 
      } catch (ExtractorException ex) { 
       if (e == null) { 
        throw new ParamException.QueryParamException(ex.getCause(), 
          parameter.getSourceName(), parameter.getDefaultValue()); 
       } else { 
        throw new ParamException.QueryParamException(ex.getCause(), 
          e.getName(), e.getDefaultValueString()); 
       } 
      } 
     } 
    } 

    private static class Resolver extends ParamInjectionResolver<VaryingParam> { 

     public Resolver() { 
      super(VaryingParamValueFactoryProvider.class); 
     } 
    } 

    public static class Binder extends AbstractBinder { 

     @Override 
     protected void configure() { 
      bind(VaryingParamValueFactoryProvider.class) 
        .to(ValueFactoryProvider.class) 
        .in(Singleton.class); 
      bind(VaryingParamValueFactoryProvider.Resolver.class) 
        .to(new TypeLiteral<InjectionResolver<VaryingParam>>() { 
        }) 
        .in(Singleton.class); 
     } 
    } 
} 

Вам нужно будет зарегистрировать этот класс Binder (низ класса) с Джерси, чтобы использовать его.

Что отличает этот класс от Jersey QueryParamValueFactoryProvider, так это то, что вместо обработки только одного значения String аннотации он разделяет это значение и пытается извлечь значения из карты параметров запроса. Первое найденное значение будет возвращено.Если параметр равен List или Set, он просто продолжает искать все варианты и добавлять их в список.

По большей части это сохраняет все функциональные возможности, которые вы ожидаете от аннотации @XxxParam. Единственное, что было трудно реализовать (так что я отказался от поддержки этого варианта использования), это несколько параметров, например.

@GET 
@Path("multiple") 
public String getMultipleVariants(@VaryingParam("param-1|param-2|param-3") String value1, 
            @VaryingParam("param-1|param-2|param-3") String value2) { 
    return value1 + ":" + value2; 
} 

Я на самом деле не думаю, что это должно быть то, что трудно осуществить, если вам действительно нужно, это просто вопрос о создании нового MultivaluedMap, удаление значения, если он найден. Это будет реализовано в методе provide() вышеприведенного значения VaryingParamFactory. Если вам нужен этот вариант использования, вы можете просто использовать List или Set.

См. this GitHub Gist (это довольно длинный) для полного тестового примера, с использованием тестовой системы Джерси. Вы можете увидеть все случаи использования, которые я тестировал в QueryTestResource, и где я регистрирую Binder с помощью ResourceConfig в методе теста configure().