Support reading object by other identifiers than id

Therefore split adapter class for single entity and collection handling.
This commit is contained in:
René Pfeuffer
2018-07-03 13:11:18 +02:00
parent 0768b638ed
commit 1bcc35d48b
10 changed files with 254 additions and 50 deletions

View File

@@ -26,43 +26,14 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class ResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
ResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager) {
CollectionResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager) {
super(manager);
}
/**
* Reads the model object for the given id, transforms it to a dto and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
MODEL_OBJECT modelObject = manager.get(id);
if (modelObject == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
DTO dto = mapToDto.apply(modelObject);
return Response.ok(dto).build();
}
/**
* Update the model object for the given id according to the given function and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
MODEL_OBJECT existingModelObject = manager.get(id);
if (existingModelObject == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
if (!id.equals(changedModelObject.getId())) {
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
}
return update(id, changedModelObject);
}
/**
* Reads all model objects in a paged way, maps them using the given function and returns a corresponding http response.
* This handles all corner cases, eg. missing privileges.

View File

@@ -1,13 +1,23 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.*;
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.group.Group;
import sonia.scm.group.GroupException;
import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.io.IOException;
@@ -19,14 +29,14 @@ public class GroupCollectionResource {
private final GroupCollectionToDtoMapper groupCollectionToDtoMapper;
private final ResourceLinks resourceLinks;
private final ResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
private final IdResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
@Inject
public GroupCollectionResource(GroupManager manager, GroupDtoToGroupMapper dtoToGroupMapper, GroupCollectionToDtoMapper groupCollectionToDtoMapper, ResourceLinks resourceLinks) {
this.dtoToGroupMapper = dtoToGroupMapper;
this.groupCollectionToDtoMapper = groupCollectionToDtoMapper;
this.resourceLinks = resourceLinks;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager);
}
/**

View File

@@ -22,14 +22,14 @@ public class GroupResource {
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
private final GroupDtoToGroupMapper dtoToGroupMapper;
private final ResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
private final IdResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
@Inject
public GroupResource(GroupManager manager, GroupToGroupDtoMapper groupToGroupDtoMapper,
GroupDtoToGroupMapper groupDtoToGroupMapper) {
this.groupToGroupDtoMapper = groupToGroupDtoMapper;
this.dtoToGroupMapper = groupDtoToGroupMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager);
}
/**

View File

@@ -0,0 +1,51 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.PageResult;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Facade for {@link SingleResourceManagerAdapter} and {@link CollectionResourceManagerAdapter}.
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> {
private final Manager<MODEL_OBJECT, EXCEPTION> manager;
private final SingleResourceManagerAdapter<MODEL_OBJECT, DTO, EXCEPTION> singleAdapter;
private final CollectionResourceManagerAdapter<MODEL_OBJECT, DTO, EXCEPTION> collectionAdapter;
IdResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager) {
this.manager = manager;
singleAdapter = new SingleResourceManagerAdapter<>(manager);
collectionAdapter = new CollectionResourceManagerAdapter<>(manager);
}
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
return singleAdapter.get(() -> manager.get(id), mapToDto);
}
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
return singleAdapter.update(() -> manager.get(id), applyChanges);
}
public Response getAll(int page, int pageSize, String sortBy, boolean desc, Function<PageResult<MODEL_OBJECT>, CollectionDto> mapToDto) {
return collectionAdapter.getAll(page, pageSize, sortBy, desc, mapToDto);
}
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws IOException, EXCEPTION {
return collectionAdapter.create(dto, modelObjectSupplier, uriCreator);
}
public Response delete(String id) {
return singleAdapter.delete(id);
}
}

View File

@@ -19,12 +19,14 @@ public class RepositoryResource {
private final RepositoryToRepositoryDtoMapper repositoryToDtoMapper;
private final ResourceManagerAdapter<Repository, RepositoryDto, RepositoryException> adapter;
private final RepositoryManager manager;
private final SingleResourceManagerAdapter<Repository, RepositoryDto, RepositoryException> adapter;
@Inject
public RepositoryResource(RepositoryToRepositoryDtoMapper repositoryToDtoMapper, RepositoryManager manager) {
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new SingleResourceManagerAdapter<>(manager);
}
@GET
@@ -39,6 +41,6 @@ public class RepositoryResource {
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name) {
return adapter.get("31QwjAKOK2", repositoryToDtoMapper::map);
return adapter.get(() -> manager.getByNamespace(namespace, name), repositoryToDtoMapper::map);
}
}

View File

@@ -0,0 +1,77 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.api.rest.resources.AbstractManagerResource;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
/**
* Adapter from resource http endpoints to managers.
*
* Provides common CRUD operations and DTO to Model Object mapping to keep Resources more DRY.
*
* @param <MODEL_OBJECT> The type of the model object, eg. {@link sonia.scm.user.User}.
* @param <DTO> The corresponding transport object, eg. {@link UserDto}.
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
SingleResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager) {
super(manager);
}
/**
* Reads the model object for the given id, transforms it to a dto and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
Response get(Supplier<MODEL_OBJECT> reader, Function<MODEL_OBJECT, DTO> mapToDto) {
MODEL_OBJECT modelObject = reader.get();
if (modelObject == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
DTO dto = mapToDto.apply(modelObject);
return Response.ok(dto).build();
}
/**
* Update the model object for the given id according to the given function and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
public Response update(Supplier<MODEL_OBJECT> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
MODEL_OBJECT existingModelObject = reader.get();
if (existingModelObject == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
if (!getId(existingModelObject).equals(getId(changedModelObject))) {
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
}
return update(getId(existingModelObject), changedModelObject);
}
@Override
protected GenericEntity<Collection<MODEL_OBJECT>> createGenericEntity(Collection<MODEL_OBJECT> modelObjects) {
throw new UnsupportedOperationException();
}
@Override
protected String getId(MODEL_OBJECT item) {
return item.getId();
}
@Override
protected String getPathPart() {
throw new UnsupportedOperationException();
}
}

View File

@@ -1,13 +1,23 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.*;
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.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.io.IOException;
@@ -18,14 +28,14 @@ public class UserCollectionResource {
private final UserCollectionToDtoMapper userCollectionToDtoMapper;
private final ResourceLinks resourceLinks;
private final ResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
@Inject
public UserCollectionResource(UserManager manager, UserDtoToUserMapper dtoToUserMapper,
UserCollectionToDtoMapper userCollectionToDtoMapper, ResourceLinks resourceLinks) {
this.dtoToUserMapper = dtoToUserMapper;
this.userCollectionToDtoMapper = userCollectionToDtoMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager);
this.resourceLinks = resourceLinks;
}

View File

@@ -23,13 +23,13 @@ public class UserResource {
private final UserDtoToUserMapper dtoToUserMapper;
private final UserToUserDtoMapper userToDtoMapper;
private final ResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
@Inject
public UserResource(UserDtoToUserMapper dtoToUserMapper, UserToUserDtoMapper userToDtoMapper, UserManager manager) {
this.dtoToUserMapper = dtoToUserMapper;
this.userToDtoMapper = userToDtoMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager);
}
/**