All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.swisspush.gateleen.expansion.ExpansionHandler Maven / Gradle / Ivy

The newest version!
package org.swisspush.gateleen.expansion;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.swisspush.gateleen.core.http.RequestLoggerFactory;
import org.swisspush.gateleen.core.storage.ResourceStorage;
import org.swisspush.gateleen.core.util.ExpansionDeltaUtil;
import org.swisspush.gateleen.core.util.ExpansionDeltaUtil.CollectionResourceContainer;
import org.swisspush.gateleen.core.util.ExpansionDeltaUtil.SlashHandling;
import org.swisspush.gateleen.core.util.HttpServerRequestUtil;
import org.swisspush.gateleen.core.util.ResourceCollectionException;
import org.swisspush.gateleen.core.util.ResponseStatusCodeLogUtil;
import org.swisspush.gateleen.core.util.StatusCode;
import org.swisspush.gateleen.routing.Rule;
import org.swisspush.gateleen.routing.RuleFeaturesProvider;
import org.swisspush.gateleen.routing.RuleProvider;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static org.swisspush.gateleen.core.util.StatusCode.INTERNAL_SERVER_ERROR;
import static org.swisspush.gateleen.core.util.StatusCode.PAYLOAD_TOO_LARGE;
import static org.swisspush.gateleen.routing.RuleFeatures.Feature.EXPAND_ON_BACKEND;
import static org.swisspush.gateleen.routing.RuleFeatures.Feature.STORAGE_EXPAND;
import static org.swisspush.gateleen.routing.RuleProvider.RuleChangesObserver;

/**
 * The {@link ExpansionHandler} allows to fetch multiple a collection of multiple resources in
 * a single GET request. To use the {@link ExpansionHandler} the parameter {@code expand=x} has to be added.
 * Having a request to a resource defining a collection of resources like this:
 *
 * 
 * {"acls":["admin","developer"]}
 * 
*

* Would lead to a result like this: * *

 * {
 *  "acls" : {
 *    "admin" : {
 *      "all.edit": {
 *        "methods": [
 *          "GET",
 *          "PUT",
 *          "POST",
 *          "DELETE"
 *        ],
 *        "path": "/.*"
 *      }
 *    },
 *    "developer" : {
 *      "role_1.menu.display": {},
 *      "role_2.menu.display": {},
 *      "gateleen.admin.menu": {}
 *    }
 *  }
 * }
 * 
* * @author https://github.com/mcweba [Marc-Andre Weber], https://github.com/ljucam [Mario Ljuca] */ public class ExpansionHandler implements RuleChangesObserver { private Logger log = LoggerFactory.getLogger(ExpansionHandler.class); public static final String SERIOUS_EXCEPTION = "a serious exception happend "; public static final String EXPAND_PARAM = "expand"; public static final String ZIP_PARAM = "zip"; private static final int NO_PARAMETER_FOUND = -1; private static final int START_INDEX = 0; private static final int TIMEOUT = 120000; private static final int DECREMENT_BY_ONE = 1; private static final int MAX_RECURSION_LEVEL = 0; private static final String EXPAND_REQUEST_METRIC = "gateleen.expand.requests"; private static final String STORAGE_EXPAND_REQUEST_METRIC = "gateleen.storage.expand.requests"; private static final String LEVEL = "level"; public static final String MAX_EXPANSION_LEVEL_SOFT_PROPERTY = "max.expansion.level.soft"; public static final String MAX_EXPANSION_LEVEL_HARD_PROPERTY = "max.expansion.level.hard"; public static final String MAX_SUBREQUEST_PROPERTY = "max.expansion.subrequests"; private static final int MAX_SUBREQUEST_COUNT_DEFAULT = 20000; private static final String ETAG_HEADER = "Etag"; private static final String SELF_REQUEST_HEADER = "x-self-request"; private static final Handler DEV_NULL = buf -> {}; private int maxSubRequestCount; private int maxExpansionLevelSoft = Integer.MAX_VALUE; private int maxExpansionLevelHard = Integer.MAX_VALUE; private HttpClient httpClient; private Map properties; private String serverRoot; private RuleProvider ruleProvider; /** * A list of parameters, which are always removed * from all requests. */ private List parameter_to_remove_for_all_request; /** * A list of parameters, which are removed after the * initial request. */ private List parameter_to_remove_after_initial_request; private RuleFeaturesProvider ruleFeaturesProvider = new RuleFeaturesProvider(new ArrayList<>()); private final Map counterMap = new HashMap<>(); private Counter storageExpandCounter; /** * Creates a new instance of the ExpansionHandler. * * @param ruleProvider {@link RuleProvider} * @param httpClient httpClient * @param properties properties * @param serverRoot serverRoot */ public ExpansionHandler(RuleProvider ruleProvider, HttpClient httpClient, final Map properties, String serverRoot) { this.httpClient = httpClient; this.properties = properties; this.serverRoot = serverRoot; initParameterRemovalLists(); initConfigurationValues(); this.ruleProvider = ruleProvider; this.ruleProvider.registerObserver(this); } /** * Creates a new instance of the ExpansionHandler. * * @param vertx vertx * @param storage storage * @param httpClient httpClient * @param properties properties * @param serverRoot serverRoot * @param rulesPath rulesPath */ public ExpansionHandler(Vertx vertx, final ResourceStorage storage, HttpClient httpClient, final Map properties, String serverRoot, final String rulesPath) { this(new RuleProvider(vertx, rulesPath, storage, properties), httpClient, properties, serverRoot); } @Override public void rulesChanged(List rules) { log.info("Update expandOnBackend and storageExpand information from changed routing rules"); ruleFeaturesProvider = new RuleFeaturesProvider(rules); } public int getMaxExpansionLevelSoft() { return maxExpansionLevelSoft; } public int getMaxExpansionLevelHard() { return maxExpansionLevelHard; } public int getMaxSubRequestCount() { return maxSubRequestCount; } public void setMeterRegistry(MeterRegistry meterRegistry) { counterMap.clear(); if(meterRegistry != null) { counterMap.put(0, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "0").register(meterRegistry)); counterMap.put(1, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "1").register(meterRegistry)); counterMap.put(2, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "2").register(meterRegistry)); counterMap.put(3, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "3").register(meterRegistry)); counterMap.put(4, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "4").register(meterRegistry)); storageExpandCounter = Counter.builder(STORAGE_EXPAND_REQUEST_METRIC).register(meterRegistry); } } private void incrementExpandReqCount(int level) { Counter counter = counterMap.get(level); if(counter != null) { counter.increment(); } } /** * Initialize the lists which defines, when which parameter * is removed (if any). */ private void initParameterRemovalLists() { parameter_to_remove_for_all_request = new ArrayList<>(); parameter_to_remove_after_initial_request = new ArrayList<>(); /* * Parameter which have to be removed for * all requests * ----- */ parameter_to_remove_for_all_request.add(EXPAND_PARAM); // ----- /* * Parameter which have to be removed after * the initial request is done * ----- */ parameter_to_remove_after_initial_request.addAll(parameter_to_remove_for_all_request); parameter_to_remove_after_initial_request.add("limit"); parameter_to_remove_after_initial_request.add("offset"); // ----- } /** * Initialize the configuration values. */ private void initConfigurationValues() { if (properties != null && properties.containsKey(MAX_SUBREQUEST_PROPERTY)) { try { maxSubRequestCount = Integer.parseInt((String) properties.get(MAX_SUBREQUEST_PROPERTY)); log.info("Setting maximum allowed subrequest count to {} from properties", maxSubRequestCount); } catch (Exception e) { maxSubRequestCount = MAX_SUBREQUEST_COUNT_DEFAULT; log.warn("Setting maximum allowed subrequest count to a default of {}, since defined " + "value for {} in properties is not a number", maxSubRequestCount, MAX_SUBREQUEST_PROPERTY); } } else { maxSubRequestCount = MAX_SUBREQUEST_COUNT_DEFAULT; log.warn("Setting maximum allowed subrequest count to a default of {}, since no property {} is defined!", maxSubRequestCount, MAX_SUBREQUEST_PROPERTY); } if (properties != null && properties.containsKey(MAX_EXPANSION_LEVEL_SOFT_PROPERTY)) { try { maxExpansionLevelSoft = Integer.parseInt((String) properties.get(MAX_EXPANSION_LEVEL_SOFT_PROPERTY)); log.info("Setting maximum expansion level soft value to {} from properties", maxExpansionLevelSoft); } catch (Exception e) { log.warn("Setting maximum expansion level soft value to a default of {}, since defined value for {} " + "in properties is not a number", maxExpansionLevelSoft, MAX_EXPANSION_LEVEL_SOFT_PROPERTY); } } else { log.info("Setting maximum expansion level soft value to a default of {}, since no property {} is defined!", maxExpansionLevelSoft, MAX_EXPANSION_LEVEL_SOFT_PROPERTY); } if (properties != null && properties.containsKey(MAX_EXPANSION_LEVEL_HARD_PROPERTY)) { try { maxExpansionLevelHard = Integer.parseInt((String) properties.get(MAX_EXPANSION_LEVEL_HARD_PROPERTY)); log.info("Setting maximum expansion level hard value to {} from properties", maxExpansionLevelHard); } catch (Exception e) { log.warn("Setting maximum expansion level hard value to a default of {}, since defined value for {} " + "in properties is not a number", maxExpansionLevelHard, MAX_EXPANSION_LEVEL_HARD_PROPERTY); } } else { log.info("Setting maximum expansion level soft hard to a default of {}, since no property {} is defined!", maxExpansionLevelHard, MAX_EXPANSION_LEVEL_HARD_PROPERTY); } } /** * Returns true when the {@link ExpansionHandler} can deal with the given request. * The request must be a GET request and must have the parameter {@code expand=x}. * The method called after this check must be handleExpansionRecursion( ... ). * * @param request request * @return boolean */ public boolean isExpansionRequest(HttpServerRequest request) { return HttpMethod.GET == request.method() && request.params().contains(EXPAND_PARAM) && !isBackendExpand(request.uri()); } /** * Check to see whether this request will be expanded by the backend (and therefore the expansionhandler * won't do anything). * * @param uri uri to check against the internal list of expandOnBackend urls * @return boolean and true if the expand should be done by the backend */ protected boolean isBackendExpand(String uri) { return ruleFeaturesProvider.isFeatureRequest(EXPAND_ON_BACKEND, uri); } /** * Check to see whether this request is a storageExpand request (will be expanded by in the storage) (and therefore the expansionhandler * won't do the subrequests by itself). * * @param uri uri to check against the internal list of storageExpand urls * @return boolean and true if the expand should be done in the storage */ protected boolean isStorageExpand(String uri) { return ruleFeaturesProvider.isFeatureRequest(STORAGE_EXPAND, uri); } /** * Returns true when the {@link ExpansionHandler} can deal with the given request. * The request must be a GET request and must have the parameter {@code expand=x&zip=true}. * The method called after this check must be handleZipRecursion( ... ). * * @param request request * @return boolean */ public boolean isZipRequest(HttpServerRequest request) { boolean ok = false; if (HttpMethod.GET == request.method() && request.params().contains(ZIP_PARAM)) { ok = !request.params().get(ZIP_PARAM).equalsIgnoreCase("false"); } return ok && !isBackendExpand(request.uri()); } /** * Makes a request to get the collection with the names of the resources to fetch. * A result of this first request could look like this: * *
     * {"acls":["admin","developer"]}
     * 
*

* In this example the name of the collection is acls. * * @param req - the request of the collection to fetch * @param recursiveHandlerType - the desired typ of the recursion functionality */ private void handleExpansionRequest(final HttpServerRequest req, final RecursiveHandlerFactory.RecursiveHandlerTypes recursiveHandlerType) { req.pause(); Logger log = RequestLoggerFactory.getLogger(ExpansionHandler.class, req); /* * in order to get the recursion level, * we need to call getRecursionDepth before * constructing the uri. The reason lies in * the referenced multimap (params), passed * to the construction method. She empties * the multimap and therefore we don't have * the possibility anymore to get the wished * parameter! */ Integer expandLevel = extractExpandParamValue(req, log); if (expandLevel == null) { respondBadRequest(req, "Expand parameter is not valid. Must be a positive number"); return; } if (expandLevel > maxExpansionLevelHard) { String message = "Expand level '" + expandLevel + "' is greater than the maximum expand level '" + maxExpansionLevelHard + "'"; log.info(message); respondBadRequest(req, message); return; } if (expandLevel > maxExpansionLevelSoft) { log.warn("Expand level '{}' is greater than the maximum soft expand level '{}'. Using '{}' instead", expandLevel, maxExpansionLevelSoft, maxExpansionLevelSoft); expandLevel = maxExpansionLevelSoft; } if (isStorageExpand(req.uri()) && expandLevel > 1) { respondBadRequest(req, "Expand values higher than 1 are not supported for storageExpand requests"); return; } // store the parameters for later use Set originalParams = null; if (req.params() != null) { originalParams = req.params().names(); } final Set finalOriginalParams = originalParams; final String targetUri = ExpansionDeltaUtil.constructRequestUri(req.path(), req.params(), parameter_to_remove_for_all_request, null, SlashHandling.END_WITH_SLASH); log.debug("constructed uri for request: {}", targetUri); Integer finalExpandLevel = expandLevel; incrementExpandReqCount(finalExpandLevel); httpClient.request(HttpMethod.GET, targetUri).onComplete(asyncReqResult -> { if (asyncReqResult.failed()) { log.warn("Failed request to {}: {}", targetUri, asyncReqResult.cause()); return; } HttpClientRequest cReq = asyncReqResult.result(); Handler> resultHandler = asyncResult -> { HttpClientResponse cRes = asyncResult.result(); HttpServerRequestUtil.prepareResponse(req, cRes); if (log.isTraceEnabled()) { log.trace(" x-delta for {} is {}", targetUri, cRes.headers().get("x-delta")); } cRes.bodyHandler(data -> { /* * TODO: * We need the possibility define parameters, which are * only used for the very FIRST request. * After the first request they must be removed. */ /* * start the recursive get * in order to guarantee, that not * endless request are made (for * example because of a loop), * the count of the subrequests is * limited. If this limit is reached * an exception is thrown and handled * by the handler right away. */ makeResourceSubRequest(targetUri, req, finalExpandLevel, new AtomicInteger(), recursiveHandlerType, RecursiveHandlerFactory.createRootHandler(recursiveHandlerType, req, serverRoot, data, finalOriginalParams), true); }); cRes.exceptionHandler(ExpansionDeltaUtil.createResponseExceptionHandler(req, targetUri, ExpansionHandler.class)); }; if (log.isTraceEnabled()) { log.trace("set cReq headers"); } cReq.idleTimeout(TIMEOUT); cReq.headers().setAll(req.headers()); cReq.headers().set("Accept", "application/json"); cReq.headers().set(SELF_REQUEST_HEADER, "true"); cReq.setChunked(true); if (log.isTraceEnabled()) { log.trace("set request data handler"); } req.handler(data -> { if (log.isTraceEnabled()) { log.trace("write data of the last subrequest"); } cReq.write(data); }); if (log.isTraceEnabled()) { log.trace("set request end handler"); } req.endHandler(v -> { cReq.send(resultHandler); log.debug("Request done"); }); cReq.exceptionHandler(ExpansionDeltaUtil.createRequestExceptionHandler(req, targetUri, ExpansionHandler.class)); if (log.isTraceEnabled()) { log.trace("resume request"); } req.resume(); }); } private Integer extractExpandParamValue(final HttpServerRequest request, final Logger log) { String expandValue = request.params().get(EXPAND_PARAM); log.debug("got expand parameter value {}", expandValue); try { int value = Integer.parseInt(expandValue); if (value < 0) { log.warn("expand parameter value '{}' is not a positive number", expandValue); return null; } return value; } catch (NumberFormatException ex) { log.warn("expand parameter value '{}' is not a valid number", expandValue); return null; } } /** * Handles the request as if it is a recursive expansion. * * @param request request */ public void handleExpansionRecursion(final HttpServerRequest request) { removeZipParameter(request); handleExpansionRequest(request, RecursiveHandlerFactory.RecursiveHandlerTypes.EXPANSION); } /** * Handles the request as if it is a request for a zip stream. * * @param request request */ public void handleZipRecursion(final HttpServerRequest request) { // default RecursiveHandlerFactory.RecursiveHandlerTypes zipType = RecursiveHandlerFactory.RecursiveHandlerTypes.ZIP; try { // override (eg. store) zipType = RecursiveHandlerFactory.RecursiveHandlerTypes.valueOf(request.params().get(ZIP_PARAM).toUpperCase()); } catch (Exception handled) { } if (log.isTraceEnabled()) { log.trace("currently using zip mode: {}", zipType); } removeZipParameter(request); handleExpansionRequest(request, zipType); } /** * Removes the zip parameter, it's only needed by the check * method isZipRequest(...). * * @param request request */ private void removeZipParameter(final HttpServerRequest request) { if (request.params().contains(ZIP_PARAM)) { request.params().remove(ZIP_PARAM); } } private void makeStorageExpandRequest(final String targetUri, final List subResourceNames, final HttpServerRequest req, final DeltaHandler handler) { Logger log = RequestLoggerFactory.getLogger(ExpansionHandler.class, req); HttpMethod reqMethod = HttpMethod.POST; String reqUri = targetUri + "?storageExpand=true"; if(storageExpandCounter != null) { storageExpandCounter.increment(); } httpClient.request(reqMethod, reqUri).onComplete(asyncResult -> { if (asyncResult.failed()) { log.warn("Failed request to {}", reqUri, asyncResult.cause()); return; } HttpClientRequest cReq = asyncResult.result(); JsonObject requestPayload = new JsonObject(); requestPayload.put("subResources", new JsonArray(subResourceNames)); Buffer payload = Buffer.buffer(requestPayload.encodePrettily()); cReq.idleTimeout(TIMEOUT); cReq.headers().setAll(req.headers()); cReq.headers().set(SELF_REQUEST_HEADER, "true"); cReq.headers().set("Content-Type", "application/json; charset=utf-8"); cReq.headers().set("Content-Length", "" + payload.length()); cReq.setChunked(false); cReq.write(payload); cReq.send(event -> { if (event.failed()) { Throwable ex = event.cause(); log.debug("{} {}", reqMethod, reqUri, ex); var exWrappr = new ResourceCollectionException(ex.getMessage(), INTERNAL_SERVER_ERROR); handler.handle(new ResourceNode(SERIOUS_EXCEPTION, exWrappr)); } HttpClientResponse cRes = event.result(); if (StatusCode.NOT_FOUND.getStatusCode() == cRes.statusCode()) { log.debug("NotFound: {}", targetUri); cRes.handler(DEV_NULL); handler.handle(new ResourceNode(SERIOUS_EXCEPTION, new ResourceCollectionException(cRes.statusMessage(), StatusCode.NOT_FOUND))); return; } if (StatusCode.METHOD_NOT_ALLOWED.getStatusCode() == cRes.statusCode()) { log.error("storageExpand not allowed for: {}", targetUri); cRes.handler(DEV_NULL); handler.handle(new ResourceNode(SERIOUS_EXCEPTION, new ResourceCollectionException(cRes.statusMessage(), StatusCode.METHOD_NOT_ALLOWED))); return; } cRes.bodyHandler(data -> { if (StatusCode.PAYLOAD_TOO_LARGE.getStatusCode() == cRes.statusCode()) { String fullResponseBody = data.toString(); log.info("{}: {}: {}", PAYLOAD_TOO_LARGE, targetUri, fullResponseBody); handler.handle(new ResourceNode(SERIOUS_EXCEPTION, new ResourceCollectionException(fullResponseBody, PAYLOAD_TOO_LARGE))); } else if (StatusCode.INTERNAL_SERVER_ERROR.getStatusCode() == cRes.statusCode()) { String fullResponseBody = data.toString(); log.error("{}: {}: {}", INTERNAL_SERVER_ERROR, targetUri, fullResponseBody); handler.handle(new ResourceNode(SERIOUS_EXCEPTION, new ResourceCollectionException(fullResponseBody, StatusCode.INTERNAL_SERVER_ERROR))); } else { String eTag = geteTag(cRes.headers()); handleSimpleResource(removeParameters(targetUri), handler, data, eTag); } }); }); }); } /** * Performs a recursive, asynchronous GET operation on the given uri. * * @param targetUri - uri for creating a new request * @param req - the original request * @param recursionLevel - the actual depth of the recursion * @param subRequestCounter - the request counter * @param recursionHandlerType - the type of the desired handler * @param handler - the parent handler * @param collection - indicates if the just passed targetUri belongs to a collection or a resource */ private void makeResourceSubRequest(final String targetUri, final HttpServerRequest req, final int recursionLevel, final AtomicInteger subRequestCounter, final RecursiveHandlerFactory.RecursiveHandlerTypes recursionHandlerType, final DeltaHandler handler, final boolean collection) { Logger log = RequestLoggerFactory.getLogger(ExpansionHandler.class, req); /* * each call of this method creates a self request. * In order not to exceed the limit of allowed * subrequests, every call increments the subRequestCounter. * If the subRequestCounter reaches maxSubrequestCount, an exception is * created and the recursive get process is canceled. */ if (subRequestCounter.get() > maxSubRequestCount) { handler.handle(new ResourceNode(SERIOUS_EXCEPTION, new ResourceCollectionException("Number of allowed sub requests exceeded. Limit is " + maxSubRequestCount + " requests", StatusCode.BAD_REQUEST))); return; } subRequestCounter.incrementAndGet(); // request target uri httpClient.request(HttpMethod.GET, targetUri).onComplete(asyncResult -> { if (asyncResult.failed()) { log.warn("Failed request to {}: {}", targetUri, asyncResult.cause()); return; } HttpClientRequest cReq = asyncResult.result(); if (log.isTraceEnabled()) { log.trace("set the cReq headers for the subRequest"); } cReq.idleTimeout(TIMEOUT); cReq.headers().setAll(req.headers()); cReq.headers().set("Accept", "application/json"); cReq.headers().set(SELF_REQUEST_HEADER, "true"); cReq.setChunked(true); cReq.exceptionHandler(ExpansionDeltaUtil.createRequestExceptionHandler(req, targetUri, ExpansionHandler.class)); if (log.isTraceEnabled()) { log.trace("end the cReq for the subRequest"); } cReq.send(event -> { HttpClientResponse cRes = event.result(); if (log.isTraceEnabled()) { log.trace(" x-delta for {} is {}", targetUri, cRes.headers().get("x-delta")); } handler.storeXDeltaResponseHeader(cRes.headers().get("x-delta")); cRes.bodyHandler(data -> { /* * extract eTag from response, it can be used for the collection, as well as the resource */ String eTag = geteTag(cRes.headers()); if (StatusCode.NOT_FOUND.getStatusCode() == cRes.statusCode()) { log.debug("requested resource could not be found: {}", targetUri); handler.handle(new ResourceNode(SERIOUS_EXCEPTION, new ResourceCollectionException(cRes.statusMessage(), StatusCode.NOT_FOUND))); } else if (StatusCode.INTERNAL_SERVER_ERROR.getStatusCode() == cRes.statusCode()) { log.debug("error in request resource : {}", targetUri); handler.handle(new ResourceNode(SERIOUS_EXCEPTION, new ResourceCollectionException(cRes.statusMessage(), StatusCode.INTERNAL_SERVER_ERROR))); } else { /* * If the request is marked as a request for a collection, * the handler for collections is invoked. * This handler can throw an exception if the indicated request * doesn't point to a collection. * This can only happen at the beginning of the recursion and * indicates the incorrect use of the parameter "expand". In * this case the handling is passed to the simple resource handler, * which is capable of even handling exceptions. */ if (collection) { try { handleCollectionResource(removeParameters(targetUri), req, recursionLevel, subRequestCounter, recursionHandlerType, handler, data, eTag); } catch (ResourceCollectionException e) { if (log.isTraceEnabled()) { log.trace("handling collection failed with: {}", e.getMessage()); } handleSimpleResource(removeParameters(targetUri), handler, data, eTag); } } else { handleSimpleResource(removeParameters(targetUri), handler, data, eTag); } } }); }); }); } /** * Handles the response data for creating a simple resource with content. * * @param targetUri - uri for creating a new request * @param handler - the parent handler * @param data - the data from the response of the request * @param eTag - eTag of the actual request */ private void handleSimpleResource(final String targetUri, final Handler handler, final Buffer data, final String eTag) { String resourceName = ExpansionDeltaUtil.extractCollectionFromPath(targetUri); if (log.isTraceEnabled()) { log.trace("Simple resource: {}", resourceName); } // pure data is passed to the handler handler.handle(new ResourceNode(resourceName, data, eTag, targetUri)); } /** * Removes all parameters from the targetUri. * * @param targetUri targetUri * @return String */ private String removeParameters(String targetUri) { int parameterIndex = targetUri.lastIndexOf('?'); if (parameterIndex != NO_PARAMETER_FOUND) { return targetUri.substring(START_INDEX, parameterIndex); } return targetUri; } /** * Respond the request with a statuscode {@link StatusCode#BAD_REQUEST} and body. * * @param request the request to respond to * @param body the body to respond */ private void respondBadRequest(final HttpServerRequest request, String body) { ResponseStatusCodeLogUtil.info(request, StatusCode.BAD_REQUEST, ExpansionHandler.class); request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()); request.response().setStatusMessage(StatusCode.BAD_REQUEST.getStatusMessage()); request.response().end(body); request.resume(); } /** * Handles the response data for creating a collection resource. * * @param targetUri - uri for creating a new request * @param req - the original request * @param recursionLevel - the actual depth of the recursion * @param subRequestCounter - the request counter * @param recursionHandlerType - the typ of the desired handler * @param handler - the parent handler * @param data - the data from the response of the request * @param eTag - eTag of the actual request * @throws ResourceCollectionException - thrown if the response does not belong to a collection */ private void handleCollectionResource(final String targetUri, final HttpServerRequest req, final int recursionLevel, final AtomicInteger subRequestCounter, final RecursiveHandlerFactory.RecursiveHandlerTypes recursionHandlerType, final DeltaHandler handler, final Buffer data, final String eTag) throws ResourceCollectionException { CollectionResourceContainer collectionResourceContainer = ExpansionDeltaUtil.verifyCollectionResponse(targetUri, data, null); Logger log = RequestLoggerFactory.getLogger(ExpansionHandler.class, req); if (log.isTraceEnabled()) { log.trace("Collection resource: {}", collectionResourceContainer.getCollectionName()); log.trace("actual recursion level: {}", recursionLevel); } // list of all subresources (if any) List subResourceNames = collectionResourceContainer.getResourceNames(); if (subResourceNames.size() == 0) { if (log.isTraceEnabled()) { log.trace("No sub resource available for: {}", collectionResourceContainer.getCollectionName()); } ResourceNode node = new ResourceNode(collectionResourceContainer.getCollectionName(), new JsonArray(), eTag); handler.handle(node); } else { boolean maxRecursionLevelReached = recursionLevel == MAX_RECURSION_LEVEL; // normal processing if (!maxRecursionLevelReached) { if (log.isTraceEnabled()) { log.trace("max. recursion reached for {}", collectionResourceContainer.getCollectionName()); } final DeltaHandler parentHandler = RecursiveHandlerFactory.createHandler(recursionHandlerType, subResourceNames, collectionResourceContainer.getCollectionName(), eTag, handler); if (isStorageExpand(targetUri)) { makeStorageExpandRequest(targetUri, subResourceNames, req, handler); } else { for (String childResourceName : subResourceNames) { if (log.isTraceEnabled()) { log.trace("processing child resource: {}", childResourceName); } // if the child is not a collection, we remove the parameter boolean collection = isCollection(childResourceName); final String collectionURI = ExpansionDeltaUtil.constructRequestUri(targetUri, req.params(), parameter_to_remove_after_initial_request, childResourceName, SlashHandling.END_WITHOUT_SLASH); makeResourceSubRequest((collection ? collectionURI : removeParameters(collectionURI)), req, recursionLevel - DECREMENT_BY_ONE, subRequestCounter, recursionHandlerType, parentHandler, collection); } } } // max. level reached else { JsonArray jsonArray = new JsonArray(); for (String childResourceName : subResourceNames) { if (log.isTraceEnabled()) { log.trace("(max level reached) processing child resource: {}", childResourceName); } jsonArray.add(childResourceName); } handler.handle(new ResourceNode(collectionResourceContainer.getCollectionName(), jsonArray, eTag)); } } } /** * Returns true if the given name or path belongs * to a collection. * In this case ends with a slash. * * @param target target * @return boolean */ public boolean isCollection(String target) { return target.endsWith("/"); } /** * Extracts the eTag from the given headers.
* If none eTag is found, an empty string is returned. * * @param headers headers * @return String */ private String geteTag(MultiMap headers) { return headers != null && headers.contains(ETAG_HEADER) ? headers.get(ETAG_HEADER) : ""; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy