Skip to content

Commit

Permalink
refactoring, increase test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
abuttaro committed Feb 15, 2018
1 parent d424064 commit 0a94082
Show file tree
Hide file tree
Showing 67 changed files with 1,254 additions and 359 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Up! Framework aims to simplify building and testing RESTful services.
* Uses consistent conventions for API parameters to support sparse fields requests, sorting, pagination, and filtering of resources
* Is not coupled to other persistence, serialization, or web application frameworks, making your APIs completely portable.
* Is designed to support PATCH operations correctly and easily
* Provides flexible, filter based services promoting composition and reuse.
* Supports flexible, filter based services promoting composition and reuse.
* Provides content negotiation to support multiple json response formats (json, json api, or hal)
* Supports [JSR 303 Bean Validation](http://beanvalidation.org/1.0/spec/)
* Supports automatic validation of resource relationships (unless configured otherwise)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
package com.github.restup.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.restup.controller.model.MediaType;
import com.github.restup.controller.model.ResourceControllerRequest;
import com.github.restup.controller.model.ResourceControllerResponse;
import com.github.restup.errors.DebugRequestError;
import com.github.restup.errors.ErrorObjectException;
import com.github.restup.errors.RequestError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.restup.errors.RequestErrorException;

/**
* Default {@link ExceptionHandler} which ensures all Exceptions are wrapped
* with an {@link ErrorObjectException} for detailed error responses.
* with an {@link RequestErrorException} for detailed error responses.
*
* @author abuttaro
*
*/
public class DefaultExceptionHandler implements ExceptionHandler {
class DefaultExceptionHandler implements ExceptionHandler {

private final static Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class);

private static volatile DefaultExceptionHandler instance = null;

private DefaultExceptionHandler() {
super();
}

public static DefaultExceptionHandler getInstance() {
if (instance == null) {
synchronized (DefaultExceptionHandler.class) {
if (instance == null) {
instance = new DefaultExceptionHandler();
}
}
}
return instance;
}

@Override
public Object handleException(ResourceControllerRequest request, ResourceControllerResponse response, Throwable e) {
ErrorObjectException result;
if (e instanceof ErrorObjectException) {
result = (ErrorObjectException) e;
RequestErrorException result;
if (e instanceof RequestErrorException) {
result = (RequestErrorException) e;
if (log.isDebugEnabled()) {
for (RequestError err : result.getErrors()) {
if (err instanceof DebugRequestError) {
Expand All @@ -36,7 +53,7 @@ public Object handleException(ResourceControllerRequest request, ResourceControl
}
} else {
log.error("An unexpected error occurred", e);
result = new ErrorObjectException(e);
result = new RequestErrorException(e);
}

response.setHeader("Content-Type", request != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ public interface ExceptionHandler {

Object handleException(ResourceControllerRequest request, ResourceControllerResponse response, Throwable e);


static ExceptionHandler getDefaultInstance() {
return DefaultExceptionHandler.getInstance();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import com.github.restup.controller.request.parser.RequestParamParser;
import com.github.restup.controller.request.parser.RequestParser;
import com.github.restup.controller.settings.ControllerSettings;
import com.github.restup.errors.ErrorCodeStatus;
import com.github.restup.errors.ErrorObjectException;
import com.github.restup.errors.StatusCode;
import com.github.restup.errors.RequestErrorException;
import com.github.restup.errors.RequestError;
import com.github.restup.mapping.fields.MappedField;
import com.github.restup.query.criteria.ResourcePathFilter.Operator;
Expand Down Expand Up @@ -208,15 +208,15 @@ private static void validateIds(ResourceControllerRequest request, ControllerMet
}
}

private static ErrorObjectException error(ResourceControllerRequest request, String code, String detail) {
private static RequestErrorException error(ResourceControllerRequest request, String code, String detail) {
return error(request).code(code).title("Not supported").detail(detail).buildException();
}

private static RequestError.Builder error(ResourceControllerRequest request) {
return RequestError.builder().resource(request.getResource());
}

private static ErrorObjectException itemResourceNotAllowed(ResourceControllerRequest request,
private static RequestErrorException itemResourceNotAllowed(ResourceControllerRequest request,
ControllerMethodAccess access) {
List<HttpMethod> supported = new ArrayList<HttpMethod>(HttpMethod.values().length - 1);
for (HttpMethod m : HttpMethod.values()) {
Expand All @@ -227,7 +227,7 @@ private static ErrorObjectException itemResourceNotAllowed(ResourceControllerReq
return methodNotAllowed(request, access, supported);
}

private static ErrorObjectException collectionResourceNotAllowed(ResourceControllerRequest request,
private static RequestErrorException collectionResourceNotAllowed(ResourceControllerRequest request,
ControllerMethodAccess access) {
List<HttpMethod> supported = new ArrayList<HttpMethod>(HttpMethod.values().length - 1);
for (HttpMethod m : HttpMethod.values()) {
Expand All @@ -238,10 +238,10 @@ private static ErrorObjectException collectionResourceNotAllowed(ResourceControl
return methodNotAllowed(request, access, supported);
}

private static ErrorObjectException methodNotAllowed(ResourceControllerRequest request,
private static RequestErrorException methodNotAllowed(ResourceControllerRequest request,
ControllerMethodAccess access, List<HttpMethod> supported) {
return error(request).status(ErrorCodeStatus.METHOD_NOT_ALLOWED)
.code(ErrorCodeStatus.METHOD_NOT_ALLOWED.name()).meta(HttpHeader.Allow.name(), supported)
return error(request).status(StatusCode.METHOD_NOT_ALLOWED)
.code(StatusCode.METHOD_NOT_ALLOWED.name()).meta(HttpHeader.Allow.name(), supported)
// TODO The response MUST query an Allow header containing a list of valid
// methods for the requested resource.
.buildException();
Expand Down Expand Up @@ -337,7 +337,7 @@ <T, ID extends Serializable> Object requestInternal(ResourceControllerRequest re

/**
* @return ContentNegotiator for content type passed
* @throws ErrorObjectException if content type is not supported
* @throws RequestErrorException if content type is not supported
*/
private ContentNegotiator getContentNegotiator(ResourceControllerRequest request) {
for (ContentNegotiator contentNegotiator : contentNegotiators) {
Expand All @@ -346,7 +346,7 @@ private ContentNegotiator getContentNegotiator(ResourceControllerRequest request
}
}
throw RequestError.builder(request.getResource())
.status(ErrorCodeStatus.UNSUPPORTED_MEDIA_TYPE)
.status(StatusCode.UNSUPPORTED_MEDIA_TYPE)
.meta(ContentTypeNegotiation.CONTENT_TYPE, request.getContentType())
.buildException();
}
Expand Down Expand Up @@ -390,10 +390,6 @@ private <T> ParsedResourceControllerRequest<T> parseRequest(ResourceControllerRe
return builder.build();
}

public enum Feature {
DISCOVERY_ENDPOINT
}

public static class Builder {

ControllerSettings.Builder settings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public DefaultContentNegotiator(String mediaType, ContentNegotiator... negotiato
this(getContentNegotiator(mediaType, negotiators));
}

private static ContentNegotiator getContentNegotiator(String mediaType, ContentNegotiator[] negotiators) {
static ContentNegotiator getContentNegotiator(String mediaType, ContentNegotiator... negotiators) {
for (ContentNegotiator negotiator : negotiators) {
if (negotiator instanceof ContentTypeNegotiation) {
if (((ContentTypeNegotiation) negotiator).getContentType().equals(mediaType)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
package com.github.restup.controller.linking;

import com.github.restup.annotations.field.RelationshipType;
import com.github.restup.controller.linking.discovery.ServiceDiscovery;
import com.github.restup.controller.model.HttpMethod;
import com.github.restup.controller.model.ParsedResourceControllerRequest;
import com.github.restup.controller.request.parser.params.PageLimitParser;
import com.github.restup.controller.request.parser.params.PageOffsetParser;
import com.github.restup.registry.Resource;
import com.github.restup.service.model.ResourceData;
import com.github.restup.service.model.response.PagedResult;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
Expand All @@ -18,6 +9,15 @@
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.restup.annotations.field.RelationshipType;
import com.github.restup.controller.linking.discovery.ServiceDiscovery;
import com.github.restup.controller.model.HttpMethod;
import com.github.restup.controller.model.ParsedResourceControllerRequest;
import com.github.restup.controller.request.parser.params.PageLimitParser;
import com.github.restup.controller.request.parser.params.PageOffsetParser;
import com.github.restup.registry.Resource;
import com.github.restup.service.model.ResourceData;
import com.github.restup.service.model.response.PagedResult;

/**
* Default implementation of {@link LinkBuilder}
Expand All @@ -42,7 +42,7 @@ public List<Link> getLinks(ParsedResourceControllerRequest<?> request, Object re
if (HttpMethod.DELETE == request.getMethod()) {
return Collections.emptyList();
}
if (request.getRelationship() != null && !isList(result)) {
if (request.getRelationship() != null && !isIterable(result)) {
Object rel = getId(request);
return Arrays.asList(link(request, LinkRelations.self, request.getRelationship(), rel, resource),
link(request, LinkRelations.related, request.getRelationship(), rel));
Expand Down Expand Up @@ -79,7 +79,7 @@ public List<Link> getTopLevelLinks(ParsedResourceControllerRequest<?> request, O
PagedResult<?> paged = (PagedResult<?>) result;
Integer offset = paged.getOffset();
Integer limit = paged.getLimit();
if (offset != null && limit != null) {
if (offset != null && limit != null && limit > 0) {
if (offset > 0) {
paging(baseUrl, request, links, LinkRelations.first, 0, limit);
paging(baseUrl, request, links, LinkRelations.prev, offset - 1, limit);
Expand All @@ -97,7 +97,7 @@ public List<Link> getTopLevelLinks(ParsedResourceControllerRequest<?> request, O
} else {
links.add(new BasicLink(LinkRelations.self, baseUrl));
}
} else if (isList(result)) {
} else if (isIterable(result)) {
links.add(new BasicLink(LinkRelations.self, baseUrl));
} else {
return links;
Expand All @@ -108,11 +108,13 @@ public List<Link> getTopLevelLinks(ParsedResourceControllerRequest<?> request, O
return links;
}

private boolean isList(Object result) {
private boolean isIterable(Object result) {
if (result instanceof ResourceData) {
if (((ResourceData<?>) result).getData() instanceof Iterable) {
return true;
}
} else if (result instanceof Iterable) {
return true;
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public abstract class BasicResourceControllerRequest implements ResourceControll
private final String baseRequestUrl;
private final String requestUrl;

protected BasicResourceControllerRequest(HttpMethod method, Resource<?, ?> resource, List<?> ids, Resource<?, ?> relationship, ResourceRelationship<?, ?, ?, ?> resourceRelationship, ResourceData<?> body
protected BasicResourceControllerRequest(HttpMethod method, Resource<?, ?> resource, List<?> ids, Resource<?, ?> relationship,
ResourceRelationship<?, ?, ?, ?> resourceRelationship, ResourceData<?> body
, String contentType, String baseRequestUrl, String requestUrl) {
this.resource = resource;
this.ids = ids;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import com.github.restup.bind.converter.ParameterConverter;
import com.github.restup.bind.converter.ParameterConverterFactory;
import com.github.restup.bind.param.ParameterProvider;
import com.github.restup.errors.ErrorCodeStatus;
import com.github.restup.errors.ErrorObjectException;
import com.github.restup.errors.StatusCode;
import com.github.restup.errors.RequestErrorException;
import com.github.restup.errors.Errors;
import com.github.restup.errors.RequestError;
import com.github.restup.registry.Resource;
Expand Down Expand Up @@ -63,14 +63,14 @@ private static String getPath(String[] path, int current) {
return current >= 0 && current < path.length ? path[current] : null;
}

private static ErrorObjectException invalidPath(String requestPath) {
private static RequestErrorException invalidPath(String requestPath) {
return RequestError.builder()
.code("INVALID_PATH")
.detail("{0} is not a valid path", requestPath)
.buildException();
}

private static ErrorObjectException invalidPath(String requestPath, String[] parts) {
private static RequestErrorException invalidPath(String requestPath, String[] parts) {
int n = 0;
if (parts.length > 3) {
n = parts.length - 3;
Expand All @@ -82,16 +82,16 @@ private static ErrorObjectException invalidPath(String requestPath, String[] par
return invalidPath(requestPath, resourceName);
}

private static ErrorObjectException invalidPath(String requestPath, String resourceName) {
private static RequestErrorException invalidPath(String requestPath, String resourceName) {
return RequestError.builder()
.code("INVALID_RESOURCE_PATH")
.detail("{0} is not a valid resource", resourceName)
.meta("resource", resourceName)
.status(ErrorCodeStatus.NOT_FOUND)
.status(StatusCode.NOT_FOUND)
.buildException();
}

private static ErrorObjectException invalidRelationship(Resource<?, ?> a, Resource<?, ?> b) {
private static RequestErrorException invalidRelationship(Resource<?, ?> a, Resource<?, ?> b) {
return RequestError.builder()
.code("INVALID_RELATIONSHIP")
.title("Unknown relationship")
Expand All @@ -101,7 +101,7 @@ private static ErrorObjectException invalidRelationship(Resource<?, ?> a, Resour
.buildException();
}

private static ErrorObjectException invalidRelationship(Resource<?, ?> a, Resource<?, ?> b, boolean toMany) {
private static RequestErrorException invalidRelationship(Resource<?, ?> a, Resource<?, ?> b, boolean toMany) {
return RequestError.builder()
.code("INVALID_RELATIONSHIP_TYPE")
.title("Invalid relationship")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.github.restup.controller.model.ParsedResourceControllerRequest;
import com.github.restup.controller.model.ResourceControllerRequest;
import com.github.restup.errors.RequestError;
import com.github.restup.errors.ErrorCodeStatus;
import com.github.restup.errors.StatusCode;

/**
* Simply throws an unsupported media type error. May be added to {@link RequestParserChain} as last
Expand All @@ -15,7 +15,7 @@ public class UnsupportedMediaTypeBodyRequestParser implements RequestParser {
@Override
public void parse(ResourceControllerRequest request, ParsedResourceControllerRequest.Builder<?> builder) {
throw RequestError.builder(request.getResource())
.status(ErrorCodeStatus.UNSUPPORTED_MEDIA_TYPE)
.status(StatusCode.UNSUPPORTED_MEDIA_TYPE)
.meta(ContentTypeNegotiation.CONTENT_TYPE, request.getContentType())
.buildException();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.github.restup.controller.settings;

import com.github.restup.controller.ExceptionHandler;
import com.github.restup.controller.content.negotiation.ContentNegotiator;
import com.github.restup.controller.interceptor.RequestInterceptor;
import com.github.restup.controller.request.parser.RequestParser;
import com.github.restup.registry.ResourceRegistry;

public class BasicControllerSettings implements ControllerSettings {

private final ResourceRegistry registry;
private final ContentNegotiator[] contentNegotiators;
private final RequestInterceptor requestInterceptor;
private final RequestParser requestParser;
private final ExceptionHandler exceptionHandler;
private final String defaultMediaType;

protected BasicControllerSettings(ResourceRegistry registry, ContentNegotiator[] contentNegotiators,
RequestInterceptor requestInterceptor, RequestParser requestParsers,
ExceptionHandler exceptionHandler, String defaultMediaType) {
super();
this.registry = registry;
this.contentNegotiators = contentNegotiators;
this.requestInterceptor = requestInterceptor;
this.requestParser = requestParsers;
this.exceptionHandler = exceptionHandler;
this.defaultMediaType = defaultMediaType;
}

@Override
public String getDefaultMediaType() {
return defaultMediaType;
}

@Override
public ResourceRegistry getRegistry() {
return registry;
}

@Override
public ContentNegotiator[] getContentNegotiators() {
return contentNegotiators;
}

@Override
public RequestInterceptor getRequestInterceptor() {
return requestInterceptor;
}

@Override
public RequestParser getRequestParser() {
return requestParser;
}

@Override
public ExceptionHandler getExceptionHandler() {
return exceptionHandler;
}

}
Loading

0 comments on commit 0a94082

Please sign in to comment.