2015-12-30 17 views
5

Có thể cho phép nhiều@QueryParam khóa cho một đối tượng/biến duy nhất trong Jersey không?Nhiều khóa @QueryParam cho một giá trị trong Jersey

enter image description here

thực tế:

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

như vậy, nếu tôi thêm ?customer-number=3 sau URL nó hoạt động.

Dự kiến ​​:

Tôi muốn nhận được các hành vi trên nếu tôi thêm bất kỳ các giá trị sau:

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

Obs:

  1. Các QueryParam chú thích trông giống như:

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

    như vậy, nó có thể không chấp nhận nhiều giá trị String (như @Produces).

  2. Phương pháp dưới đây cho phép người dùng sử dụng nhiều phím có ý nghĩa tương tự cùng một lúc (và tôi muốn có một "OR" tình trạng giữa chúng):

    @POST 
    public Something getThings(@QueryParam("customer-number") Integer n1, 
              @QueryParam("customerNumber") Integer n2, 
              @QueryParam("customerNo") Integer n3) { 
        ... 
    } 
    
  3. Something như doesn này 't công việc:

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

làm thế nào tôi có thể làm điều này?

chi tiết:

  • Jersey 2.22.1
  • Java 8

Trả lời

1

Có lẽ cách đơn giản nhất và dễ nhất là nên sử dụng một tùy chỉnh @BeanParam:

Đầu tiên xác định đậu tùy chỉnh sáp nhập tất cả các thông số truy vấn như:

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; 
    } 
} 

và sau đó sử dụng nó với @BeanParam trong phương pháp tài nguyên của bạn:

public Something getThings(
    @BeanParam MergedIntegerValue n) { 

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

Tham chiếu: https://jersey.java.net/documentation/latest/user-guide.html#d0e2403

1

Thành thật mà nói: đây không phải là cách thiết kế webservices được thiết kế. Bạn đặt ra một hợp đồng nghiêm ngặt mà cả khách hàng và máy chủ đều tuân theo; bạn xác định một tham số và đó là nó.

Nhưng tất nhiên nó sẽ là một thế giới hoàn hảo, nơi bạn có quyền tự do để quyết định những gì sẽ xảy ra. Vì vậy, nếu bạn phải cho phép ba tham số trong, thì bạn sẽ phải làm cho rằng hợp đồng. Đây là một cách tiếp cận sau đây # 2 mà tôi phải cung cấp mà không thể kiểm tra nó cho goofs:

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; 
} 

Vì vậy, về cơ bản từ chối bất kỳ cuộc gọi mà không vượt qua đặc biệt là một trong những thông số, và để cho một mapper ngoại lệ dịch ngoại lệ bạn ném vào mã phản hồi HTTP trong phạm vi 4xx của khóa học.

(Tôi đã tạo phương thức getNonNullValue() tĩnh là nó tấn công tôi như một hàm tiện ích có thể dùng lại).

1

Bạn có thể tạo chú thích tùy chỉnh. Tôi sẽ không đi quá nhiều về cách thực hiện, bạn có thể xem this post hoặc this post. Về cơ bản nó dựa trên một cơ sở hạ tầng khác với tiêm phụ thuộc thông thường với Jersey. Bạn có thể xem this package từ dự án Jersey. Đây là nơi tất cả các nhà cung cấp tiêm sống xử lý tiêm @XxxParam. Nếu bạn kiểm tra mã nguồn, bạn sẽ thấy việc triển khai khá giống nhau. Hai liên kết tôi đã cung cấp ở trên theo cùng một mẫu, cũng như mã bên dưới.

Những gì tôi đã làm là tạo ra một chú thích tùy chỉnh

@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; 
     } 
    } 
} 

Nó có vẻ lạ rằng tôi có một nhà máy để tạo ra nó, nhưng điều này đã được yêu cầu để thực hiện các mã dưới đây, nơi tôi chia giá trị của String và kết thúc tạo một cá thể chú thích mới cho mỗi giá trị phân tách.

Dưới đây là ValueFactoryProvider (trong đó, nếu bạn đã đọc một trong các bài viết trên, bạn sẽ thấy điều đó là bắt buộc đối với việc chèn thông số phương pháp tùy chỉnh). Nó là một lớp học lớn, chỉ vì tôi đặt tất cả các lớp học cần thiết vào một lớp duy nhất, theo mẫu bạn thấy trong dự án Jersey.

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); 
     } 
    } 
} 

Bạn cần đăng ký lớp học 'Binder (cuối lớp) với Jersey để sử dụng.

Điều khác biệt lớp này với Jersey QueryParamValueFactoryProvider là thay vì chỉ xử lý một giá trị chuỗi duy nhất của chú thích, nó chia tách giá trị và cố gắng trích xuất các giá trị từ bản đồ param truy vấn. Giá trị đầu tiên được tìm thấy sẽ được trả lại.Nếu tham số là List hoặc Set, nó chỉ tiếp tục tìm kiếm tất cả các tùy chọn và thêm chúng vào danh sách.

Phần lớn điều này giữ tất cả chức năng bạn mong đợi từ chú thích @XxxParam. Điều duy nhất khó thực hiện (vì vậy tôi đã bỏ qua hỗ trợ trường hợp sử dụng này), là nhiều thông số, ví dụ:

@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; 
} 

Tôi thực sự không nghĩ rằng nó nên là khó để thực hiện, nếu bạn thực sự cần nó, nó chỉ là vấn đề của việc tạo ra một mới MultivaluedMap, loại bỏ một giá trị nếu nó được tìm thấy. Điều này sẽ được triển khai theo phương pháp provide() của số VaryingParamFactory ở trên. Nếu bạn cần trường hợp sử dụng này, bạn chỉ có thể sử dụng một số List hoặc Set để thay thế.

Xem this GitHub Gist (nó khá dài) cho một trường hợp thử nghiệm hoàn chỉnh, sử dụng Khung kiểm tra Jersey. Bạn có thể xem tất cả các trường hợp sử dụng tôi đã kiểm tra trong số QueryTestResource và nơi tôi đăng ký Binder bằng ResourceConfig trong phương pháp thử nghiệm configure().

Các vấn đề liên quan