2016-10-27 4 views
2

나는 Spring Data REST을 사용하여 노출 된 간단한 UserRepository을 가지고 있습니다.스프링 데이터 REST : 단일 리소스의 투영 표현

@Document(collection = User.COLLECTION_NAME) 
@Setter 
@Getter 
public class User extends Entity { 

    public static final String COLLECTION_NAME = "users"; 

    private String name; 
    private String email; 
    private String password; 
    private Set<UserRole> roles = new HashSet<>(0); 
} 

나는 다음과 같은 방법을 보이는 UserProjection 클래스를 만들었습니다 : 나는 '

@RepositoryRestResource(collectionResourceRel = User.COLLECTION_NAME, path = RestPath.Users.ROOT, 
     excerptProjection = UserProjection.class) 
public interface RestUserRepository extends MongoRepository<User, String> { 

    // Not exported operations 

    @RestResource(exported = false) 
    @Override 
    <S extends User> S insert(S entity); 

    @RestResource(exported = false) 
    @Override 
    <S extends User> S save(S entity); 

    @RestResource(exported = false) 
    @Override 
    <S extends User> List<S> save(Iterable<S> entites); 
} 

: 여기

@JsonInclude(JsonInclude.Include.NON_NULL) 
@Projection(types = User.class) 
public interface UserProjection { 

    String getId(); 

    String getName(); 

    String getEmail(); 

    Set<UserRole> getRoles(); 
} 

이 저장소 클래스를 여기 는 User 엔티티 클래스 구성에서 사용자 투영을 지정하여 사용되는지 확인했습니다. 내가 할 때

config.getProjectionConfiguration().addProjection(UserProjection.class, User.class); 

그래서, /사용자에을 GET 경로를 내가 응답 다음 (투사가 적용됩니다) 얻을 :

{ 
    "_embedded" : { 
    "users" : [ { 
     "name" : "Yuriy Yunikov", 
     "id" : "5812193156aee116256a33d4", 
     "roles" : [ "USER", "ADMIN" ], 
     "email" : "[email protected]", 
     "points" : 0, 
     "_links" : { 
     "self" : { 
      "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4" 
     }, 
     "user" : { 
      "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4{?projection}", 
      "templated" : true 
     } 
     } 
    } ] 
    }, 
    "_links" : { 
    "self" : { 
     "href" : "http://127.0.0.1:8080/users" 
    }, 
    "profile" : { 
     "href" : "http://127.0.0.1:8080/profile/users" 
    } 
    }, 
    "page" : { 
    "size" : 20, 
    "totalElements" : 1, 
    "totalPages" : 1, 
    "number" : 0 
    } 
} 

을하지만, 내가 만들려고 할 때 GET은 단일 리소스를 요구합니다. /사용자/5812193156aee116256a33d4, 나는 다음과 같은 응답을 얻을 :

{ 
    "name" : "Yuriy Yunikov", 
    "email" : "[email protected]", 
    "password" : "123456", 
    "roles" : [ "USER", "ADMIN" ], 
    "_links" : { 
    "self" : { 
     "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4" 
    }, 
    "user" : { 
     "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4{?projection}", 
     "templated" : true 
    } 
    } 
} 

당신이 볼 수있는 바와 같이, 암호 필드가 돌려지고 투사가 적용되지 않습니다. 나는 민감한 자료의 자료를 숨길 수있는 @JsonIgnore 어노테이션이 있다는 것을 알고있다. 그러나 내 User 개체는 API 또는 JSON 표현에 대해 알지 못하는 다른 응용 프로그램 모듈에 있으므로 주석 필드에 @JsonIgnore 주석을 표시하는 것은 의미가 없습니다.

@Oliver Gierke의 here이 발췌 록이 단일 자원에 자동으로 적용되지 않는 이유에 대한 게시물을 보았습니다. 그러나, 그것은 여전히 ​​내 경우에 매우 불편하고 단일 리소스를 얻을 때 같은 UserProjection을 반환하고 싶습니다. 어떻게 든 사용자 정의 컨트롤러를 만들거나 @JsonIgnore 필드를 표시하지 않고 그것을 할 수 있습니까?

+1

'ResourceProcessor'를 사용하여이 효과 (또는 비슷한 효과)를 얻을 수 있습니다. https://jira.spring.io/browse/DATAREST-428의 댓글에 잠재적으로 유용한 토론이 있습니다. – CollinD

+0

@CollinD 훌륭한 링크를 제공해 주셔서 감사합니다! 이런 경우에는'ResourceProcessor' 나'ResourceAssembler'가 맞는 해결책이 될 것 같습니다. 그러나 나는 아직도 Spring Data REST에서 주석이나 설정이 없다는 것에 대해 궁금해한다. –

답변

5

내가 DATAREST-428에 제안 모든 리소스에 대한 예측을 적용 ResourceProcessor 클래스를 생성 할 수 있었다. 그것은 다음과 같은 방식으로 작동합니다 : 투영 매개 변수가 URL에 지정된 경우 - 지정된 투영법이 적용됩니다. 그렇지 않은 경우 - 이름이 default 인 투영법이 반환되고 처음 적용된 투영법이 적용됩니다. 또한 링크를 무시하는 사용자 정의 을 추가해야합니다. 그렇지 않으면 반환하는 JSON에 두 개의 _links 키가 있습니다.

/** 
* Projecting resource used for {@link ProjectingProcessor}. Does not include empty links in JSON, otherwise two 
* _links keys are present in returning JSON. 
* 
* @param <T> 
*/ 
@JsonInclude(JsonInclude.Include.NON_EMPTY) 
class ProjectingResource<T> extends Resource<T> { 

    ProjectingResource(final T content) { 
     super(content); 
    } 
} 

/** 
* Resource processor for all resources which applies projection for single resource. By default, projections 
* are not 
* applied when working with single resource, e.g. http://127.0.0.1:8080/users/580793f642d54436e921f6ca. See 
* related issue <a href="https://jira.spring.io/browse/DATAREST-428">DATAREST-428</a> 
*/ 
@Component 
public class ProjectingProcessor implements ResourceProcessor<Resource<Object>> { 

    private static final String PROJECTION_PARAMETER = "projection"; 

    private final ProjectionFactory projectionFactory; 

    private final RepositoryRestConfiguration repositoryRestConfiguration; 

    private final HttpServletRequest request; 

    public ProjectingProcessor(@Autowired final RepositoryRestConfiguration repositoryRestConfiguration, 
           @Autowired final ProjectionFactory projectionFactory, 
           @Autowired final HttpServletRequest request) { 
     this.repositoryRestConfiguration = repositoryRestConfiguration; 
     this.projectionFactory = projectionFactory; 
     this.request = request; 
    } 

    @Override 
    public Resource<Object> process(final Resource<Object> resource) { 
     if (AopUtils.isAopProxy(resource.getContent())) { 
      return resource; 
     } 

     final Optional<Class<?>> projectionType = findProjectionType(resource.getContent()); 
     if (projectionType.isPresent()) { 
      final Object projection = projectionFactory.createProjection(projectionType.get(), resource 
        .getContent()); 
      return new ProjectingResource<>(projection); 
     } 

     return resource; 
    } 

    private Optional<Class<?>> findProjectionType(final Object content) { 
     final String projectionParameter = request.getParameter(PROJECTION_PARAMETER); 
     final Map<String, Class<?>> projectionsForType = repositoryRestConfiguration.getProjectionConfiguration() 
       .getProjectionsFor(content.getClass()); 

     if (!projectionsForType.isEmpty()) { 
      if (!StringUtils.isEmpty(projectionParameter)) { 
       // projection parameter specified 
       final Class<?> projectionClass = projectionsForType.get(projectionParameter); 
       if (projectionClass != null) { 
        return Optional.of(projectionClass); 
       } 
      } else if (projectionsForType.containsKey(ProjectionName.DEFAULT)) { 
       // default projection exists 
       return Optional.of(projectionsForType.get(ProjectionName.DEFAULT)); 
      } 

      // no projection parameter specified 
      return Optional.of(projectionsForType.values().iterator().next()); 
     } 

     return Optional.empty(); 
    } 
} 
1

저는 최근에 비슷한 것을보고 있었고 스프링 데이터/잭슨 측에서 접근하려고 시도했을 때 동그라미로 돌아서기도했습니다.

대안이면서 매우 간단한 해결책은 다른 각도에서 접근하여 HTTP 요청의 Projection 매개 변수가 항상 존재하는지 확인하는 것입니다. 이것은 서블릿 필터를 사용하여 들어오는 요청의 매개 변수를 수정하여 수행 할 수 있습니다.

이는 아래와 같이 보일 것입니다 :

public class ProjectionResolverFilter extends GenericFilterBean { 

    private static final String REQUEST_PARAM_PROJECTION_KEY = "projection"; 

    @Override 
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
      throws IOException, ServletException { 

     HttpServletRequest request = (HttpServletRequest) req; 

     if (shouldApply(request)) { 
      chain.doFilter(new ResourceRequestWrapper(request), res); 
     } else { 
      chain.doFilter(req, res); 
     } 
    } 

    /** 
    * 
    * @param request 
    * @return True if this filter should be applied for this request, otherwise 
    *   false. 
    */ 
    protected boolean shouldApply(HttpServletRequest request) { 
     return request.getServletPath().matches("some-path"); 
    } 

    /** 
    * HttpServletRequestWrapper implementation which allows us to wrap and 
    * modify the incoming request. 
    * 
    */ 
    public class ResourceRequestWrapper extends HttpServletRequestWrapper { 

     public ResourceRequestWrapper(HttpServletRequest request) { 
      super(request); 
     } 

     @Override 
     public String getParameter(final String name) { 
      if (name.equals(REQUEST_PARAM_PROJECTION_KEY)) { 
       return "nameOfDefaultProjection"; 
      } 

      return super.getParameter(name); 
     } 
    } 
} 
+0

이 솔루션은 내가 관심있는 부분이 프로젝션 매개 변수를 전달하지 않고 기본 리소스 표현 인만큼 찾고자하는 대상에 맞지 않습니다. 또한 나를 위해 "해킹"처럼 보이는,'ResourceProcessor'로 그것을하는 것이 훨씬 더 낫다. @CollinD에서 제공하는 링크와 같습니다. 하지만 어쨌든 제안에 감사드립니다. –

+0

솔루션은 요청에서 전달 된 것처럼 자동으로 기본 프로젝션을 적용 할 때 프로젝션 매개 변수를 전달할 필요가 없습니다. 예, 약간의 해킹이지만 간단하고 작동합니다. 솔루션을 얻는다면 대답으로 게시 할 수 있다면 사용자 정의 리소스 프로세서가 작동하는 것을 보는 데 관심이 있습니다. –

+0

예, 좋지 않습니다. 투영 매개 변수를 전달할 필요가 없습니다. 그러나'shouldApply' 메쏘드에서 여전히 경로를 하드 코딩 할 필요가 있거나 모든 리소스에 적용 할 수 있습니까? 나중에 리소스 프로세서를 점검하고 작동한다면 대답을 게시 할 것입니다. –

관련 문제