Remove old AbstractManagerResource

This commit is contained in:
René Pfeuffer
2019-04-09 12:14:32 +02:00
parent 290f8466eb
commit c6436da455
6 changed files with 221 additions and 778 deletions

View File

@@ -1,581 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.UrlEscapers;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.LastModifiedAware;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.PageResult;
import sonia.scm.api.rest.RestExceptionResult;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.Comparables;
import sonia.scm.util.Util;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
//~--- JDK imports ------------------------------------------------------------
public abstract class AbstractManagerResource<T extends ModelObject> {
/** the logger for AbstractManagerResource */
private static final Logger logger =
LoggerFactory.getLogger(AbstractManagerResource.class);
protected final Manager<T> manager;
private final Class<T> type;
protected int cacheMaxAge = 0;
protected boolean disableCache = false;
public AbstractManagerResource(Manager<T> manager, Class<T> type) {
this.manager = manager;
this.type = type;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param items
*
* @return
*/
protected abstract GenericEntity<Collection<T>> createGenericEntity(
Collection<T> items);
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param item
*
* @return
*/
protected abstract String getId(T item);
/**
* Method description
*
*
* @return
*/
protected abstract String getPathPart();
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
*
* @param uriInfo
* @param item
*
* @return
*/
public Response create(UriInfo uriInfo, T item)
{
preCreate(item);
Response response;
try
{
manager.create(item);
String id = getId(item);
response = Response.created(location(uriInfo, id)).build();
}
catch (AuthorizationException ex)
{
logger.warn("create is not allowd", ex);
response = Response.status(Status.FORBIDDEN).build();
}
catch (Exception ex)
{
logger.error("error during create", ex);
response = createErrorResponse(ex);
}
return response;
}
@VisibleForTesting
URI location(UriInfo uriInfo, String id) {
String escaped = UrlEscapers.urlPathSegmentEscaper().escape(id);
return uriInfo.getAbsolutePath().resolve(getPathPart().concat("/").concat(escaped));
}
/**
* Method description
*
*
* @param name
*
* @return
*/
public Response delete(String name)
{
Response response = null;
T item = manager.get(name);
if (item != null)
{
preDelete(item);
try
{
manager.delete(item);
response = Response.noContent().build();
}
catch (AuthorizationException ex)
{
logger.warn("delete not allowd", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
}
catch (Exception ex)
{
logger.error("error during delete", ex);
response = createErrorResponse(ex);
}
}
return response;
}
/**
* Method description
*
*
*
*
* @param name
* @param item
*
*
* @return
*/
public Response update(String name, T item)
{
Response response = null;
preUpdate(item);
try
{
manager.modify(item);
response = Response.noContent().build();
}
catch (AuthorizationException ex)
{
logger.warn("update not allowed", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
}
catch (Exception ex)
{
logger.error("error during update", ex);
response = createErrorResponse(ex);
}
return response;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
*
* @param request
* @param id
*
* @return
*/
public Response get(Request request, String id)
{
Response response;
T item = manager.get(id);
if (item != null)
{
prepareForReturn(item);
if (disableCache)
{
response = Response.ok(item).build();
}
else
{
response = createCacheResponse(request, item, item);
}
}
else
{
response = Response.status(Response.Status.NOT_FOUND).build();
}
return response;
}
/**
* Method description
*
*
*
* @param request
* @param start
* @param limit
* @param sortby
* @param desc
* @return
*/
public Response getAll(Request request, int start, int limit, String sortby,
boolean desc)
{
Collection<T> items = fetchItems(sortby, desc, start, limit);
if (Util.isNotEmpty(items))
{
items = prepareForReturn(items);
}
Response response = null;
Object entity = createGenericEntity(items);
if (disableCache)
{
response = Response.ok(entity).build();
}
else
{
response = createCacheResponse(request, manager, items, entity);
}
return response;
}
/**
* Method description
*
*
* @return
*/
public int getCacheMaxAge()
{
return cacheMaxAge;
}
/**
* Method description
*
*
* @return
*/
public boolean isDisableCache()
{
return disableCache;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param cacheMaxAge
*/
public void setCacheMaxAge(int cacheMaxAge)
{
this.cacheMaxAge = cacheMaxAge;
}
/**
* Method description
*
*
* @param disableCache
*/
public void setDisableCache(boolean disableCache)
{
this.disableCache = disableCache;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param throwable
*
* @return
*/
protected Response createErrorResponse(Throwable throwable)
{
return createErrorResponse(Status.INTERNAL_SERVER_ERROR,
throwable.getMessage(), throwable);
}
/**
* Method description
*
*
* @param status
* @param message
* @param throwable
*
* @return
*/
protected Response createErrorResponse(Status status, String message,
Throwable throwable)
{
return Response.status(status).entity(new RestExceptionResult(message,
throwable)).build();
}
/**
* Method description
*
*
* @param item
*/
protected void preCreate(T item) {}
/**
* Method description
*
*
* @param item
*/
protected void preDelete(T item) {}
/**
* Method description
*
*
* @param item
*/
protected void preUpdate(T item) {}
/**
* Method description
*
*
* @param item
*
* @return
*/
protected T prepareForReturn(T item)
{
return item;
}
/**
* Method description
*
*
* @param items
*
* @return
*/
protected Collection<T> prepareForReturn(Collection<T> items)
{
return items;
}
/**
* Method description
*
*
* @param rb
*/
private void addCacheControl(Response.ResponseBuilder rb)
{
CacheControl cc = new CacheControl();
cc.setMaxAge(cacheMaxAge);
rb.cacheControl(cc);
}
/**
* Method description
*
*
* @param request
* @param timeItem
* @param item
* @param <I>
*
* @return
*/
private <I> Response createCacheResponse(Request request,
LastModifiedAware timeItem, I item)
{
return createCacheResponse(request, timeItem, item, item);
}
/**
* Method description
*
*
* @param request
* @param timeItem
* @param entityItem
* @param item
* @param <I>
*
* @return
*/
private <I> Response createCacheResponse(Request request,
LastModifiedAware timeItem, Object entityItem, I item)
{
Response.ResponseBuilder builder = null;
Date lastModified = getLastModified(timeItem);
EntityTag e = new EntityTag(Integer.toString(entityItem.hashCode()));
if (lastModified != null)
{
builder = request.evaluatePreconditions(lastModified, e);
}
else
{
builder = request.evaluatePreconditions(e);
}
if (builder == null)
{
builder = Response.ok(item).tag(e).lastModified(lastModified);
}
addCacheControl(builder);
return builder.build();
}
private Comparator<T> createComparator(String sortBy, boolean desc) {
Comparator<T> comparator = Comparables.comparator(type, sortBy);
if (desc) {
comparator = comparator.reversed();
}
return comparator;
}
private Collection<T> fetchItems(String sortBy, boolean desc, int start,
int limit)
{
AssertUtil.assertPositive(start);
Collection<T> items = null;
if (limit > 0)
{
if (Util.isEmpty(sortBy))
{
// replace with something useful
sortBy = "id";
}
items = manager.getAll(createComparator(sortBy, desc), start, limit);
}
else if (Util.isNotEmpty(sortBy))
{
items = manager.getAll(createComparator(sortBy, desc));
}
else
{
items = manager.getAll();
}
return items;
}
protected PageResult<T> fetchPage(String sortBy, boolean desc, int pageNumber,
int pageSize) {
AssertUtil.assertPositive(pageNumber);
AssertUtil.assertPositive(pageSize);
if (Util.isEmpty(sortBy)) {
// replace with something useful
sortBy = "id";
}
return manager.getPage(createComparator(sortBy, desc), pageNumber, pageSize);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param item
*
* @return
*/
private Date getLastModified(LastModifiedAware item)
{
Date lastModified = null;
Long l = item.getLastModified();
if (l != null)
{
lastModified = new Date(l);
}
return lastModified;
}
}

View File

@@ -4,12 +4,13 @@ import de.otto.edison.hal.HalRepresentation;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.PageResult;
import sonia.scm.api.rest.resources.AbstractManagerResource;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.Comparables;
import sonia.scm.util.Util;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Collection;
import java.util.Comparator;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -27,10 +28,14 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
DTO extends HalRepresentation>{
protected final Manager<MODEL_OBJECT> manager;
protected final Class<MODEL_OBJECT> type;
CollectionResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
super(manager, type);
this.manager = manager;
this.type = type;
}
/**
@@ -42,6 +47,27 @@ class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
return Response.ok(mapToDto.apply(pageResult)).build();
}
private PageResult<MODEL_OBJECT> fetchPage(String sortBy, boolean desc, int pageNumber,
int pageSize) {
AssertUtil.assertPositive(pageNumber);
AssertUtil.assertPositive(pageSize);
if (Util.isEmpty(sortBy)) {
// replace with something useful
sortBy = "id";
}
return manager.getPage(createComparator(sortBy, desc), pageNumber, pageSize);
}
private Comparator<MODEL_OBJECT> createComparator(String sortBy, boolean desc) {
Comparator<MODEL_OBJECT> comparator = Comparables.comparator(type, sortBy);
if (desc) {
comparator = comparator.reversed();
}
return comparator;
}
/**
* Creates a model object for the given dto and returns a corresponding http response.
* This handles all corner cases, eg. no conflicts or missing privileges.
@@ -55,18 +81,7 @@ class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
return Response.created(URI.create(uriCreator.apply(created))).build();
}
@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,15 +1,16 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.NotFoundException;
import sonia.scm.api.rest.resources.AbstractManagerResource;
import sonia.scm.api.rest.RestExceptionResult;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -29,10 +30,13 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
DTO extends HalRepresentation> {
private static final Logger LOG = LoggerFactory.getLogger(SingleResourceManagerAdapter.class);
private final Function<Throwable, Optional<Response>> errorHandler;
private final Class<MODEL_OBJECT> type;
protected final Manager<MODEL_OBJECT> manager;
protected final Class<MODEL_OBJECT> type;
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
this(manager, type, e -> Optional.empty());
@@ -42,7 +46,7 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
Manager<MODEL_OBJECT> manager,
Class<MODEL_OBJECT> type,
Function<Throwable, Optional<Response>> errorHandler) {
super(manager, type);
this.manager = manager;
this.errorHandler = errorHandler;
this.type = type;
}
@@ -75,6 +79,33 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
return update(getId(existingModelObject), changedModelObject);
}
public Response update(String name, MODEL_OBJECT item)
{
Response response = null;
preUpdate(item);
try
{
manager.modify(item);
response = Response.noContent().build();
}
catch (AuthorizationException ex)
{
LOG.warn("update not allowed", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
}
catch (Exception ex)
{
LOG.error("error during update", ex);
response = createErrorResponse(ex);
}
return response;
}
protected void preUpdate(MODEL_OBJECT item) {}
private boolean modelObjectWasModifiedConcurrently(MODEL_OBJECT existing, MODEL_OBJECT updated) {
return existing.getLastModified() != null
&& (updated.getLastModified() == null || existing.getLastModified() > updated.getLastModified());
@@ -89,23 +120,51 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
}
}
@Override
public Response delete(String name)
{
Response response = null;
MODEL_OBJECT item = manager.get(name);
if (item != null)
{
preDelete(item);
try
{
manager.delete(item);
response = Response.noContent().build();
}
catch (AuthorizationException ex)
{
LOG.warn("delete not allowd", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
}
catch (Exception ex)
{
LOG.error("error during delete", ex);
response = createErrorResponse(ex);
}
}
return response;
}
protected void preDelete(MODEL_OBJECT item) {}
protected Response createErrorResponse(Throwable throwable) {
return errorHandler.apply(throwable).orElse(super.createErrorResponse(throwable));
return errorHandler.apply(throwable)
.orElse(createErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, throwable.getMessage(), throwable));
}
@Override
protected GenericEntity<Collection<MODEL_OBJECT>> createGenericEntity(Collection<MODEL_OBJECT> modelObjects) {
throw new UnsupportedOperationException();
protected Response createErrorResponse(Response.Status status, String message,
Throwable throwable)
{
return Response.status(status).entity(new RestExceptionResult(message,
throwable)).build();
}
@Override
protected String getId(MODEL_OBJECT item) {
return item.getId();
}
@Override
protected String getPathPart() {
throw new UnsupportedOperationException();
}
}