2016-08-03 2 views
0

API 버전에 따라 정확한 속성 이름을 추출하기 위해 JacksonAnnotationIntrospector를 사용하여 사용자 정의 주석을 설정했습니다. API 버전에 따라 올바른 ObjectMapper를 내보내는 도우미 클래스가 있습니다.응답을 보낼 때 MessageBodyWriter가 사용되지 않습니다.

public class ObjectMapperFactory { 

    private static final ObjectMapper objectMapper_V1 = new ObjectMapper().setAnnotationIntrospector(new VersioningPropertiesIntrospector(Entity.ApiVersion.V1)); 
    private static final ObjectMapper objectMapper_V2016 = new ObjectMapper().setAnnotationIntrospector(new VersioningPropertiesIntrospector(Entity.ApiVersion.V2016)); 

    public static ObjectMapper getObjectMapper(Entity.ApiVersion version) { 
    switch (version) { 
     case V1: 
      return objectMapper_V1; 

     case V2016: 
      return objectMapper_V2016; 

     case INVALID: 
      return null; 
    } 

    return null; 
    } 
} 

이 같은 단위 테스트에 직렬화

public static String serializeEntity(Entity.ApiVersion version, Object object) { 
    try { 
     return getObjectMapper(version).writeValueAsString(object); 
    } catch (JsonProcessingException e) { 
     log.error(e.toString()); 
    } 

    return "Invalid API version."; 
} 

을 테스트하는 데 사용하는 도우미 기능이있다 :

: 이제

@Test 
public void testSerializeUserWithStateField() { 
    User user = new User(); 
    user.setVersion(Entity.ApiVersion.V2016); 
    user.setState(EntityState.CREATED.name()); 

    String userJson = serializeEntity(user.getVersion(), user); 

    assertThat(userJson, equalTo("{\"lifecycleState\":\"CREATED\"}")); 
} 

, 나는 이런 식으로 뭔가가 있다고

@GET 
@Path("users/{userId}") 
public Response getUser(@PrincipalContext Principal principal, 
        @AuthorizationRequestContext AuthorizationRequest authorizationRequest, 
        @PathParam("userId") String userId) { 

    final String decodedId = Optional 
     .ofNullable(RequestValidationHelper.decodeUrlEncodedOCID(userId)) 
     .filter(StringUtils::isNotEmpty) 
     .orElseThrow(BadArgumentException::new); 

    User user = userStore.getUser(decodedId) 
     .orElseThrow(OperationNotAllowedException::new); 

    log.debug("Successfully retrieved user '{}'", decodedId); 
    return Response.status(Response.Status.OK) 
      .header(HttpHeaders.ETAG, user.getEtag()) 
      .entity(user) 
      .build(); 
} 

사용자가 엔터티를 확장합니다.

public abstract class Entity { 

    private String id; 
    private String userId; 

    @JsonIgnore 
    private String etag; 

    @VersioningProperties({ 
     @VersioningProperties.Property(version = ApiVersion.V1, value = "state"), 
     @VersioningProperties.Property(version = ApiVersion.V2016, value = "lifecycleState") 
}) 
    private String state; 

    @JsonIgnore 
    private ApiVersion version = ApiVersion.INVALID; 

    public enum ApiVersion { 
    INVALID, 
    V1, 
    V2016 
    } 
} 

나는 올바른 JSON을 반환한다는 것을 알고 있습니다. 응답을 생성 할 때 .entity()에서 serializeEntity에 대한 호출을 삽입 할 수는 있지만 응답의 엔터티가 동일한 유형 (예 : User)인지 확인하는 테스트에서 문제가 발생했습니다. 단일 객체의 직렬화 된 버전이나 일련 번호가 지정된 List <>의 문자열을 찾은 경우 (예 : 어떤 경우이든) 객체가 발견됩니다.

올바르게 이해한다면 @Provider 주석을 사용하여 MessageBodyWriter를 지정하면 지정된 객체를 직렬화 할 때 (즉, Dropwizard와 Jersey를 사용할 때) @Provider 주석을 지정해야합니다.

@Provider 
public class EntityMessageBodyWriter implements MessageBodyWriter<Entity> { 
    @Override 
    public long getSize(Entity entity, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { 
    return 0; 
    } 

    @Override 
    public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { 
    return Entity.class.isAssignableFrom(aClass); 
    } 

    @Override 
    public void writeTo(Entity entity, Class<?> aClass, Type type, Annotation[] annotations, 
        MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) 
     throws IOException, WebApplicationException { 
    outputStream.write(serializeEntity(entity.getVersion(), entity).getBytes()); 
    } 
} 

그러나 그런 경우는 아닙니다. 나는 모든 객체에 대해 별도의 MessageBodyWriter를 만들지 않았다. 문서에서 여러분은 수퍼 클래스를 사용할 수 있다고 말했고, 모든 하위 클래스 또한 일치 할 것이다. (내가했던 isWriteable() 함수에서 true를 반환한다고 가정). 또한 JSON 미디어 유형으로 @Produces를 지정하고 Entity 대신 User와 같은 하위 클래스 하나만 지정하는 등의 작업을 시도했지만 아무 것도 작동하지 않는 것 같습니다.

은 또한으로 MessageBodyWriter 등록 시도 :

JerseyEnvironment jersey = env.jersey(); 
jersey.register(new IdentityEntityMessageBodyWriter()); 

하지만 한 모든 것을 우리가 (500S, 409s 등)이 거의 모든 시험을 중단했다.

API 버전 인 state을 기반으로 변경하려는 변수가 V2016 API 호출에 대한 응답으로 lifecycleState으로 설정되지 않습니다. 어떻게하면 제대로 작동합니까?

+0

안녕하세요, 귀하의 질문에 예외가 추가 될 수 있습니까? – pandaadb

+0

예외가 실제로 throw되지 않습니다. 컴파일되고 실행되지만 속성 이름의 잘못된 버전이 튀어 나옵니다. – Gemini14

+0

메시지 작성자가 불렀습니까? serializeEntity에 중단 점을 설정할 때 무슨 일이 일어나고 있습니까? 이 문제는 프로덕션 문제입니까 (예 : 서버를 실행할 때 실패하는 것으로 보입니까) 아니면 통합 테스트 문제입니까? – pandaadb

답변

2

잘못된 것이 무엇인지 확인하기 위해 예제에서 약간 어렵습니다.

나는 이것을 DW와 통합하는 방법을 보여주는 최소한의 예제를 작성했습니다.

우선 참고 사항 :

도움이되지 않습니다 MessageBodyWriter에 주석 달기. 이 기능은 클래스를 처리하는 인젝션 프레임 워크가있을 때 작동합니다. Annotation을 사용하여이 주석이하는 Jersey와 자동으로 등록 할 수 있습니다. 따라서 DW에서 (Guicey 나 classpath 스캐닝 등을 사용하지 않는다면) 이것은 작동하지 않을 것이므로 수동으로해야합니다.

첫째, 내 주석 : Specifying different JSON property names according to API version with Jackson

모델 :

@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.FIELD}) 
public @interface VersioningProperties {  
    Property[] value(); 

    @interface Property { 
     String version(); 
     String value(); 
    } 
} 

다음, 내 주석 버전 거시기 :

public class VersioningPropertiesIntrospector extends JacksonAnnotationIntrospector { 

    private static final long serialVersionUID = 1L; 
    private String version; 

    public VersioningPropertiesIntrospector(String version) { 
     this.version = version; 
    } 

    @Override 
    public PropertyName findNameForSerialization(Annotated a) { 
     PropertyName propertyName = findNameFromVersioningProperties(a); 
     if (propertyName != null) { 
      return propertyName; 
     } 
     return super.findNameForSerialization(a); 
    } 

    @Override 
    public PropertyName findNameForDeserialization(Annotated a) { 
     PropertyName propertyName = findNameFromVersioningProperties(a); 
     if (propertyName != null) { 
      return propertyName; 
     } 
     return super.findNameForDeserialization(a); 
    } 

    private PropertyName findNameFromVersioningProperties(Annotated a) { 
     VersioningProperties annotation = a.getAnnotation(VersioningProperties.class); 
     if (annotation == null) { 
      return null; 
     } 
     for (Property property : annotation.value()) { 
      if (version.equals(property.version())) { 
       return new PropertyName(property.value()); 
      } 
     } 
     return null; 
    } 

} 

나는이 게시물에서 차용 한이 두

public class Person { 

    @VersioningProperties ({ 
     @VersioningProperties.Property(version="A", value="test1") 
     ,@VersioningProperties.Property(version="B", value="test2") 
    }) 
    public String name = UUID.randomUUID().toString(); 

    public String x = "A"; // or B 
} 

"x"속성을 사용하여 사용할 버전을 결정하고 있습니다. 나머지는 당신의 예와 비슷합니다.

"x"가 "A"인 경우 속성의 이름은 "test1"이고, 그렇지 않으면 "B"인 경우 "test2"가됩니다.

이 응용 프로그램은이 시작됩니다 :

public class Application extends io.dropwizard.Application<Configuration>{ 

    @Override 
    public void run(Configuration configuration, Environment environment) throws Exception { 

     environment.jersey().register(HelloResource.class); 

     ObjectMapper aMapper = environment.getObjectMapper().copy().setAnnotationIntrospector(new VersioningPropertiesIntrospector("A")); 
     ObjectMapper bMapper = environment.getObjectMapper().copy().setAnnotationIntrospector(new VersioningPropertiesIntrospector("B")); 
     environment.jersey().register(new MyMessageBodyWriter(aMapper, bMapper)); 
    } 

    public static void main(String[] args) throws Exception { 
     new Application().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml"); 
    } 
} 

주 내가 저지 환경과 MessageBodyWriter를 등록하고있다. DW가 이미 우리에게 제공하고있는 ObjectMapper도 사용하고 있습니다. 이 OM에는 이미 설정되고 유용하게 사용되는 몇 가지 구성 (예 : DateTime 처리 및 유사한 기능)이 있습니다.

그리고 내 자원 :

@Path("test") 
public class HelloResource { 

    @GET 
    @Path("asd") 
    @Produces(MediaType.APPLICATION_JSON) 
    public Person p(String x) { 
     Person p = new Person(); 
     p.x = x; 
     return p; 
    } 
} 

나는 GET 자원에 몸을 통과하는 나쁜 관행을 알고 있지만, 그래서 나는 무슨 일이 일어나고 있는지 설명 할 수있는 사람 속성을 전환 할 수 있습니다 만입니다.

여기 결국 내 MessageBodyWriter입니다 : 지금

public class MyMessageBodyWriter implements MessageBodyWriter<Person> { 

    private ObjectMapper aMapper; 
    private ObjectMapper bMapper; 

    MyMessageBodyWriter(ObjectMapper aMapper, ObjectMapper bMapper) { 
     this.aMapper = aMapper; 
     this.bMapper = bMapper; 
    } 

    @Override 
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 
     return Person.class.isAssignableFrom(type); 
    } 

    @Override 
    public long getSize(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 
     return 0; 
    } 

    @Override 
    public void writeTo(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, 
      MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) 
      throws IOException, WebApplicationException { 

     switch(t.x) { 
     case "A": aMapper.writeValue(entityStream, t); 
     break; 
     case "B" : bMapper.writeValue(entityStream, t); 
     break; 
     } 
    } 
} 

내 API를 호출, 내가 얻을 : 속성 이름이 올바르게 I가 통과하고 몸에 따라 전환 된

[email protected]:~/tmp/test$ curl -v -XGET "localhost:9085/api/test/asd" -d "A" 
* Trying 127.0.0.1... 
* Connected to localhost (127.0.0.1) port 9085 (#0) 
> GET /api/test/asd HTTP/1.1 
> Host: localhost:9085 
> User-Agent: curl/7.47.0 
> Accept: */* 
> Content-Length: 1 
> Content-Type: application/x-www-form-urlencoded 
> 
* upload completely sent off: 1 out of 1 bytes 
< HTTP/1.1 200 OK 
< Date: Tue, 09 Aug 2016 09:59:13 GMT 
< Content-Type: application/json 
< Vary: Accept-Encoding 
< Content-Length: 56 
< 
* Connection #0 to host localhost left intact 
{"x":"A","test1":"adec4590-47af-4eeb-a15a-67a532c22b72"}[email protected]:~/tmp/test$ 
[email protected]:~/tmp/test$ 
[email protected]:~/tmp/test$ curl -v -XGET "localhost:9085/api/test/asd" -d "B" 
* Trying 127.0.0.1... 
* Connected to localhost (127.0.0.1) port 9085 (#0) 
> GET /api/test/asd HTTP/1.1 
> Host: localhost:9085 
> User-Agent: curl/7.47.0 
> Accept: */* 
> Content-Length: 1 
> Content-Type: application/x-www-form-urlencoded 
> 
* upload completely sent off: 1 out of 1 bytes 
< HTTP/1.1 200 OK 
< Date: Tue, 09 Aug 2016 09:59:17 GMT 
< Content-Type: application/json 
< Vary: Accept-Encoding 
< Content-Length: 56 
< 
* Connection #0 to host localhost left intact 
{"x":"B","test2":"6c56650c-6c87-418f-8b1a-0750a8091c46"}[email protected]:~/tmp/test$ 

하는 것으로 내 컬 명령.

그래서 저는 왜 테스트가 실패하는지 100 % 확신하지 못합니다.

나는 AnnotationIntrospector를 앞뒤로 전환 할 수없는 OM과 관련된 일종의 캐싱이 있다고 생각한다. (이것은 내 OM을 재설정 할 수 없기 때문에 가정이다.) 어쨌든 2 가지 다른 옵션을 사용하는 것이 더 좋은 옵션 일 수 있습니다.

문제 해결에 도움이되기를 바랍니다.

테스트를 사용하는 경우 모든 테스트가 유닛 테스트에도 올바르게 등록되어 있는지 확인해야합니다.

몇 가지 중단 점, sysout 및 기타 유용한 작은 친구를 설정하면 올바른 것을 알려줍니다.

건배!

artur