Polishing during review.

Extends REST API docs, straightens naming, cleans up dependencies,
clears warnings, does a bit of formatting, etc.
This commit is contained in:
Johannes Schnatterer
2018-06-25 17:56:03 +02:00
parent e0cb5cc924
commit b299606f88
16 changed files with 86 additions and 59 deletions

View File

@@ -405,19 +405,6 @@
<scope>provided</scope>
</dependency>
<!-- Validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
</dependencies>
<build>
@@ -871,7 +858,7 @@
<dependency>
<groupId>com.webcohesion.enunciate</groupId>
<artifactId>enunciate-top</artifactId>
<version>2.9.1</version>
<version>${enunciate.version}</version>
<exclusions>
<exclusion>
<groupId>com.webcohesion.enunciate</groupId>
@@ -879,6 +866,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.webcohesion.enunciate</groupId>
<artifactId>enunciate-lombok</artifactId>
<version>${enunciate.version}</version>
</dependency>
</dependencies>
</plugin>

View File

@@ -24,7 +24,7 @@ import static java.util.Optional.ofNullable;
* the {@link JsonNode} tree.</p>
*/
@Provider
@Priority(FilterPriorities.FIELD_FILTER)
@Priority(ResponseFilterPriorities.FIELD_FILTER)
public class FieldContainerResponseFilter implements ContainerResponseFilter {
private static final String PARAMETER_FIELDS = "fields";

View File

@@ -11,12 +11,12 @@ import java.util.Map;
import static java.util.Arrays.stream;
public final class JsonFilters {
final class JsonFilters {
private JsonFilters() {
}
public static void filterByFields(JsonNode root, Collection<String> filterExpressions) {
static void filterByFields(JsonNode root, Collection<String> filterExpressions) {
createJsonFilterNode(filterExpressions).filterNode(root);
}

View File

@@ -19,7 +19,7 @@ import java.util.Set;
* and processes all registered plugins for the {@link JsonEnricher} extension point.
*/
@Provider
@Priority(FilterPriorities.JSON_MARSHALLING)
@Priority(ResponseFilterPriorities.JSON_MARSHALLING)
public class JsonMarshallingResponseFilter implements ContainerResponseFilter {
private final ObjectMapper objectMapper;

View File

@@ -5,16 +5,17 @@ import javax.ws.rs.Priorities;
/**
* A collection of filter priorities used by custom {@link javax.ws.rs.container.ContainerResponseFilter}s.
* Higher number means earlier execution in the response filter chain.
*/
final class FilterPriorities {
final class ResponseFilterPriorities {
/**
* Other filters depend on already marshalled {@link com.fasterxml.jackson.databind.JsonNode} trees. Thus, JSON
* marshalling has to happen before those filters
*/
static final int JSON_MARSHALLING = Priorities.USER + 1000;
static final int FIELD_FILTER = JSON_MARSHALLING - 1000;
static final int FIELD_FILTER = Priorities.USER;
private FilterPriorities() {
private ResponseFilterPriorities() {
}
}

View File

@@ -16,10 +16,8 @@ import static de.otto.edison.hal.Links.linkingTo;
import static sonia.scm.api.v2.resources.ResourceLinks.group;
import static sonia.scm.api.v2.resources.ResourceLinks.user;
/**
* Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
*/
@java.lang.SuppressWarnings("squid:S3306")
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto> {

View File

@@ -9,7 +9,7 @@ import java.util.Arrays;
/**
* This class is used to create links for JAX-RS resources. Create a new instance specifying all resource classes used
* to process the request. Than for each of these classes call <code>builder.method(...).parameters(...)</code> for each
* to process the request. Then for each of these classes call <code>builder.method(...).parameters(...)</code> for each
* of these classes consecutively. The builder itself is immutable, so that each instance is reusable and you get a new
* builder for each method.
*

View File

@@ -4,13 +4,16 @@ import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data @NoArgsConstructor @AllArgsConstructor
@NoArgsConstructor @AllArgsConstructor
@Data @EqualsAndHashCode(callSuper = false)
public class MemberDto extends HalRepresentation {
private String name;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}

View File

@@ -2,6 +2,7 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.PageResult;
@@ -31,12 +32,14 @@ import static sonia.scm.api.v2.resources.ResourceLinks.user;
@Produces(VndMediaType.USER_COLLECTION)
public class UserCollectionResource extends AbstractManagerResource<User, UserException> {
public static final int DEFAULT_PAGE_SIZE = 10;
private static final int DEFAULT_PAGE_SIZE = 10;
private final UserDtoToUserMapper dtoToUserMapper;
private final UserCollectionToDtoMapper userCollectionToDtoMapper;
@Inject
public UserCollectionResource(UserManager manager, UserDtoToUserMapper dtoToUserMapper, UserCollectionToDtoMapper userCollectionToDtoMapper) {
public UserCollectionResource(UserManager manager, UserDtoToUserMapper dtoToUserMapper,
UserCollectionToDtoMapper userCollectionToDtoMapper) {
super(manager);
this.dtoToUserMapper = dtoToUserMapper;
this.userCollectionToDtoMapper = userCollectionToDtoMapper;
@@ -44,50 +47,52 @@ public class UserCollectionResource extends AbstractManagerResource<User, UserEx
/**
* Returns all users for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
* <strong>Note:</strong> This method requires admin privileges.
*
* <strong>Note:</strong> This method requires "user" privileges.
*
* @param request the current request
* @param page the number of the requested page
* @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE})
* @param sortby sort parameter
* @param desc sort direction desc or aesc
* @return
* @param sortBy sort parameter
* @param desc sort direction desc or asc
*/
@GET
@Path("")
@TypeHint(UserDto[].class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@Override
public Response getAll(@Context Request request,
@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
@QueryParam("sortby") String sortby,
@DefaultValue("false")
@QueryParam("desc") boolean desc) {
PageResult<User> pageResult = fetchPage(sortby, desc, page, pageSize);
@QueryParam("sortby") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
PageResult<User> pageResult = fetchPage(sortBy, desc, page, pageSize);
return Response.ok(userCollectionToDtoMapper.map(page, pageSize, pageResult)).build();
}
/**
* Creates a new user.
*
* <strong>Note:</strong> This method requires "user" privileges.
*
* @param userDto The user to be created.
* @return A response with the link to the new user (if created successfully).
*/
@POST
@Path("")
@StatusCodes({
@ResponseCode(code = 201, condition = "create success", additionalHeaders = {
@ResponseHeader(name = "Location", description = "uri to the created group")
}),
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
@ResponseCode(code = 201, condition = "create success"),
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
public Response create(@Context UriInfo uriInfo, UserDto userDto) throws IOException, UserException {
if (userDto == null) {
return Response.status(400).build();

View File

@@ -7,10 +7,8 @@ import javax.inject.Inject;
import static sonia.scm.api.v2.resources.ResourceLinks.userCollection;
/**
* Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
*/
@java.lang.SuppressWarnings("squid:S3306")
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper<User, UserDto> {
private final UriInfoStore uriInfoStore;

View File

@@ -5,12 +5,14 @@ import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.Instant;
import java.util.Optional;
@Data @AllArgsConstructor @NoArgsConstructor
@AllArgsConstructor @NoArgsConstructor
@Data @EqualsAndHashCode(callSuper = false)
public class UserDto extends HalRepresentation {
private boolean active;
private boolean admin;
@@ -24,6 +26,7 @@ public class UserDto extends HalRepresentation {
private String type;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}

View File

@@ -11,10 +11,8 @@ import javax.inject.Inject;
import static sonia.scm.api.rest.resources.UserResource.DUMMY_PASSWORT;
/**
* Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
*/
@java.lang.SuppressWarnings("squid:S3306")
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class UserDtoToUserMapper {

View File

@@ -26,6 +26,7 @@ import java.util.Collection;
@Produces(VndMediaType.USER)
public class UserResource extends AbstractManagerResource<User, UserException> {
private final UserDtoToUserMapper dtoToUserMapper;
private final UserToUserDtoMapper userToDtoMapper;
@@ -36,6 +37,15 @@ public class UserResource extends AbstractManagerResource<User, UserException> {
this.userToDtoMapper = userToDtoMapper;
}
/**
* Returns a user.
*
* <strong>Note:</strong> This method requires "user" privileges.
*
* @param request the current request
* @param id the id/name of the user
*
*/
@GET
@Path("")
@TypeHint(UserDto.class)
@@ -54,11 +64,19 @@ public class UserResource extends AbstractManagerResource<User, UserException> {
return Response.ok(userDto).build();
}
/**
* Modifies the given user.
*
* <strong>Note:</strong> This method requires "user" privileges.
*
* @param name name of the user to be modified
* @param userDto user object to modify
*/
@PUT
@Path("")
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
@@ -69,14 +87,23 @@ public class UserResource extends AbstractManagerResource<User, UserException> {
return update(name, user);
}
/**
* Deletes a user.
*
* <strong>Note:</strong> This method requires "user" privileges.
*
* @param name the name of the user to delete.
*
*/
@DELETE
@Path("")
@StatusCodes({
@ResponseCode(code = 204, condition = "delete success"),
@ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
@ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
@Override
public Response delete(@PathParam("id") String name) {
return super.delete(name);
}

View File

@@ -4,10 +4,13 @@ import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
/**
* RESTful Web Service Resource to manage users.
*/
@Path(UserRootResource.USERS_PATH_V2)
public class UserRootResource {
public static final String USERS_PATH_V2 = "v2/users/";
static final String USERS_PATH_V2 = "v2/users/";
private final Provider<UserCollectionResource> userCollectionResource;
private final Provider<UserResource> userResource;

View File

@@ -14,10 +14,8 @@ import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static sonia.scm.api.v2.resources.ResourceLinks.user;
/**
* Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
*/
@java.lang.SuppressWarnings("squid:S3306")
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {

View File

@@ -80,7 +80,8 @@ public class UserRootResourceTest {
UserCollectionResource userCollectionResource = new UserCollectionResource(userManager, dtoToUserMapper,
userCollectionToDtoMapper);
UserResource userResource = new UserResource(dtoToUserMapper, userToDtoMapper, userManager);
UserRootResource userRootResource = new UserRootResource(MockProvider.of(userCollectionResource), MockProvider.of(userResource));
UserRootResource userRootResource = new UserRootResource(MockProvider.of(userCollectionResource),
MockProvider.of(userResource));
dispatcher.getRegistry().addSingletonResource(userRootResource);
when(uriInfo.getBaseUri()).thenReturn(URI.create("/"));