Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.swisspush.gateleen.merge;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.*;
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.util.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* Allows to perform a request over more than one route.
* The MergeHandler has to be addressed by the header x-merge-collections.
* The following example shows how to use the MergeHandler.
*
*
* Note:
* The following parameters do not work with the MergeHandler:
*
*
x-delta
*
*
*
* @author https://github.com/ljucam [Mario Aerni]
*/
public class MergeHandler {
private static Logger log = LoggerFactory.getLogger(MergeHandler.class);
public static final String MISSMATCH_ERROR = "Resources as well as collections with the given name were found.";
private static final String MERGE_HEADER = "x-merge-collections";
private static final String SELF_REQUEST_HEADER = "x-self-request";
private static final int NO_PARAMETER_FOUND = -1;
private static final int TIMEOUT = 120000;
private static final String SLASH = "/";
private final HttpClient httpClient;
private final Comparator collectionContentComparator;
/**
* Creates a new instance of the MergeHandler
*
* @param httpClient the self client
*/
public MergeHandler(HttpClient httpClient) {
this.httpClient = httpClient;
collectionContentComparator = new CollectionContentComparator();
}
/**
* Checks if the MergeHandler is responsible for this request.
* If so it processes the request and returns true, otherwise
* it returns false.
*
* @param request original request
* @return true if processed, false otherwise
*/
public boolean handle(final HttpServerRequest request) {
final String mergeCollection = request.getHeader(MERGE_HEADER);
if (mergeCollection != null && request.method().equals(HttpMethod.GET)) {
// perform a get request on the parent, to get all collections of the routes
httpClient.request(HttpMethod.GET, mergeCollection).onComplete(asyncReqResult -> {
if (asyncReqResult.failed()) {
log.warn("Failed request to {}: {}", request.uri(), asyncReqResult.cause());
return;
}
HttpClientRequest cReq = asyncReqResult.result();
cReq.idleTimeout(TIMEOUT);
cReq.headers().set("Accept", "application/json");
cReq.headers().set(SELF_REQUEST_HEADER, "true");
cReq.setChunked(true);
cReq.exceptionHandler(ExpansionDeltaUtil.createRequestExceptionHandler(request, mergeCollection, MergeHandler.class));
cReq.send(asyncResult -> {
HttpClientResponse cRes = asyncResult.result();
// everything is ok
if (cRes.statusCode() == StatusCode.OK.getStatusCode()) {
final String collectionName = getCollectionName(mergeCollection);
final String targetUrlPart = getTargetUrlPart(request.path());
if (log.isTraceEnabled()) {
log.trace("handle > (mergeCollection) {}, (collectionName) {}, (targetUrlPart) {}", mergeCollection, collectionName, targetUrlPart);
}
// process respons
cRes.handler(data -> {
JsonObject dataObject = new JsonObject(data.toString());
if (log.isTraceEnabled()) {
log.trace(" >> body is \"{}\"", dataObject);
}
// we get an array back
if (dataObject.getValue(collectionName) instanceof JsonArray) {
List collections = getCollections(dataObject.getJsonArray(collectionName));
final Handler mergeCollectionHandler = installMergeCollectionHandler(request, collections.size(), targetUrlPart);
for (final String collection : collections) {
/*
In order to perform the right request (direct request for a resource,
merge request for a collection), we are forced to first perform a
request to the underlying collection. This seems to be the only way to
distinguish weather we hare requesting a collection or a resource.
We cannot ask redis (storage), because we may perform this request
against a service behind a dynamic route.
This way we always get two requests (one for the underlying collection and one
for the wished original request).
We may however improve the performance by using the following algorithm.
First request the underlying collection (for all routes) and mark the ones,
which are not available (404 - NOT FOUND). This one’s doesn't have to be
requested again.
If an error occured (not 404), create an error response.
*/
if (log.isTraceEnabled()) {
log.trace("requestCollection {}", collection);
}
requestCollection(request, mergeCollection, collection, request.path(), mergeCollectionHandler);
}
} else {
// write the data back
request.response().end(dataObject.toBuffer());
}
});
}
// something is odd
else {
request.response().setChunked(true);
cRes.handler(data -> request.response().write(data));
cRes.endHandler(v -> request.response().end());
}
});
});
return true;
}
// the request was not processed by the MergeHandler
return false;
}
/**
* Performs a request to the parent resource of the resulting
* request. This way it is possible to determin if we have a
* resource or a collection request.
*
* @param request the original request
* @param mergeCollection the path value from the header
* @param collection the requested sub resource with trailing slash
* @param path the path component from the rule
* @param mergeCollectionHandler the merge handler
*/
private void requestCollection(final HttpServerRequest request,
final String mergeCollection,
final String collection,
final String path,
final Handler mergeCollectionHandler) {
/*
mergeCollection + collection + path
/gateleen/masterdata/parent/ collection1/ data/whatever
/gateleen/masterdata/parent/collection1/data/whatever
First request will be
parentCollection of /gateleen/masterdata/parent/collection1/data/whatever
=> /gateleen/masterdata/parent/collection1/data/
Result can be: (will result in merge request)
{
"data" : [
"whatever/"
]
}
Or: (will result in direct request)
{
"data" : [
"whatever"
]
}
*/
final String requestUrl = mergeCollection + collection +
(path.startsWith(SLASH) ? path.substring(path.indexOf(SLASH) + 1, path.length()) : path);
final String parentUrl = prepareParentCollection(requestUrl);
final String targetUrlPart = getTargetUrlPart(requestUrl);
if (log.isTraceEnabled()) {
log.trace("requestCollection > (requestUrl) {} (parentUrl) {} (targetUrlPart) {}", requestUrl, parentUrl, targetUrlPart);
}
httpClient.request(HttpMethod.GET, parentUrl).onComplete(asyncReqResult -> {
if (asyncReqResult.failed()) {
log.warn("Failed request to {}: {}", request.uri(), asyncReqResult.cause());
return;
}
HttpClientRequest collectionRequest = asyncReqResult.result();
collectionRequest.idleTimeout(TIMEOUT);
collectionRequest.headers().set("Accept", "application/json");
collectionRequest.headers().set(SELF_REQUEST_HEADER, "true");
collectionRequest.setChunked(true);
collectionRequest.exceptionHandler(ExpansionDeltaUtil.createRequestExceptionHandler(request, parentUrl, MergeHandler.class));
collectionRequest.send(asyncResult -> {
HttpClientResponse collectionResponse = asyncResult.result();
collectionResponse.exceptionHandler(ExpansionDeltaUtil.createRequestExceptionHandler(request, parentUrl, MergeHandler.class));
// everything is ok
if (collectionResponse.statusCode() == StatusCode.OK.getStatusCode()) {
final String parentCollection = getCollectionName(parentUrl);
// do superfanzy things
collectionResponse.bodyHandler(data -> {
String collectionName = parentCollection;
JsonObject dataObject = new JsonObject(data.toString());
if (!dataObject.containsKey(collectionName)) {
/*
in case the parent collection can not be found, we
most surely have a request which is performed over a
route.
E.G.
Source: /gateleen/dynamicdata/
Target: /gateleen/target/t1/
GET /gateleen/dynamicdata/
{
"t1" : [
"..."
]
}
This has to be handled in order to be able to perform
even root - GET requests.
In this case there may only exists (always) one Element
in the dataObject. This element we do need!
*/
if (dataObject.size() == 1) {
collectionName = dataObject.fieldNames().stream().findFirst().get();
if (log.isTraceEnabled()) {
log.trace(" >>> collection {} could not be found, use instead key: {}", parentCollection, collectionName);
}
}
}
if (log.isTraceEnabled()) {
log.trace("requestCollection >> uri is: {}, body is: {}", parentUrl, data);
}
if (dataObject.getValue(collectionName) instanceof JsonArray) {
List collectionContent = getCollectionContent(dataObject.getJsonArray(collectionName));
// collection
if (collectionContent.contains(targetUrlPart + SLASH)) {
mergeCollectionHandler.handle(new MergeData(
data,
collectionResponse.statusCode(),
collectionResponse.statusMessage(),
true,
requestUrl));
}
// resource
else if (collectionContent.contains(targetUrlPart)) {
mergeCollectionHandler.handle(new MergeData(
data,
collectionResponse.statusCode(),
collectionResponse.statusMessage(),
false,
requestUrl));
}
// not found
else {
mergeCollectionHandler.handle(new MergeData(
data,
StatusCode.NOT_FOUND.getStatusCode(),
StatusCode.NOT_FOUND.getStatusMessage(),
false,
requestUrl));
}
}
// this is an optimization
else {
if (log.isTraceEnabled()) {
log.trace("requestCollection >> given array was not found");
}
mergeCollectionHandler.handle(new MergeData(
data,
StatusCode.NOT_FOUND.getStatusCode(),
StatusCode.NOT_FOUND.getStatusMessage(),
false,
requestUrl));
}
});
}
// not found or something else is not ok
else {
collectionResponse.handler(data -> mergeCollectionHandler.handle(new MergeData(
data,
collectionResponse.statusCode(),
collectionResponse.statusMessage(),
false,
requestUrl)));
}
});
});
}
/**
* Returns a list of strings (with trailing slash) of
* the collection passed down as an array.
*
* @param array jsonArray
* @return list of collection strings
*/
private List getCollections(JsonArray array) {
return ((List