/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.server.router.handlers;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.kie.server.router.proxy.aggragate.ResponseAggregator;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.ResponseCodeHandler;
import io.undertow.util.HeaderValues;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
public abstract class AbstractAggregateHttpHandler implements HttpHandler {
protected static final Logger log = Logger.getLogger(AbstractAggregateHttpHandler.class);
protected static final String REPLACE_PAGE = "page=[^&]*";
protected static final String REPLACE_PAGE_SIZE = "pageSize=[^&]*";
protected static final String DEFAULT_ACCEPT = "application/xml";
protected HttpHandler httpHandler;
protected AdminHttpHandler adminHandler;
private RoundRobinHostSelector selector = new RoundRobinHostSelector();
public AbstractAggregateHttpHandler(HttpHandler httpHandler, AdminHttpHandler adminHandler) {
this.httpHandler = httpHandler;
this.adminHandler = adminHandler;
}
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
if (!exchange.getRequestMethod().equals(HttpString.tryFromString("GET"))) {
httpHandler.handleRequest(exchange);
return;
}
Map<String, Deque<String>> queryParams = exchange.getQueryParameters();
// collect and alter paging
Integer page = 0;
Integer pageSize = 10;
Deque<String> originalPage = queryParams.get("page");
if (originalPage != null && !originalPage.isEmpty()) {
page = Integer.parseInt(originalPage.getFirst());
}
Deque<String> originalPageSize = queryParams.remove("pageSize");
if (originalPageSize != null && !originalPageSize.isEmpty()) {
pageSize = Integer.parseInt(originalPageSize.getFirst());
}
final String routerPage = "0";
// need to add 1 to page for proper size of page
final String routerPageSize = String.valueOf((1 + page) * pageSize);
// collect sorting
String sortBy = null;
boolean sortOder = true;
Deque<String> originalSortBy = queryParams.get("sort");
if (originalSortBy != null && !originalSortBy.isEmpty()) {
sortBy = originalSortBy.getFirst();
}
Deque<String> originalSortOrder = queryParams.get("sortOrder");
if (originalSortOrder != null && !originalSortOrder.isEmpty()) {
sortOder = Boolean.parseBoolean(originalSortOrder.getFirst());
}
final Map<String,List<String>> responseHeaders = new ConcurrentHashMap<>();
List<String> returnResponses = getServerHosts().parallelStream().map(url -> {
String response = null;
try {
response = sendRequest(url, exchange, responseHeaders, routerPage, routerPageSize);
} catch (Exception e) {
log.error("Error when forwarding request to server", e);
}
return response;
})
.filter(msg -> msg != null && !msg.trim().isEmpty())
.collect(Collectors.toList());
if (returnResponses.isEmpty()) {
ResponseCodeHandler.HANDLE_404.handleRequest(exchange);
return;
}
HeaderValues accept = exchange.getRequestHeaders().get(Headers.ACCEPT);
HeaderValues kieContentType = exchange.getRequestHeaders().get("X-KIE-ContentType");
ResponseAggregator responseAggregator = adminHandler.getAggregators().stream().filter(a -> a.supports(kieContentType, accept, DEFAULT_ACCEPT)).findFirst().orElseThrow(() ->
new RuntimeException("not possible to find response aggregator for " + responseHeaders.get(Headers.ACCEPT))
);
String response = null;
if (supportAdvancedAggregate()) {
response = responseAggregator.aggregate(returnResponses, sortBy, sortOder, page, pageSize);
} else {
response = responseAggregator.aggregate(returnResponses);
}
responseHeaders.forEach((name, value) -> {
exchange.getResponseHeaders().putAll(HttpString.tryFromString(name), value);
});
exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, response.getBytes("UTF-8").length);
exchange.getResponseSender().send(response);
}
protected String sendRequest(String url, HttpServerExchange exchange, Map<String,List<String>> responseHeaders, String page, String pageSize) throws Exception {
URL obj = new URL(url + exchange.getRequestPath() + "?" + exchange.getQueryString().replaceAll(REPLACE_PAGE, "page=" + page).replaceAll(REPLACE_PAGE_SIZE, "pageSize="+pageSize));
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
//add request headers
exchange.getRequestHeaders().forEach(h -> {
con.setRequestProperty(h.getHeaderName().toString(), h.getFirst());
});
log.debugf("Sending 'GET' request to URL : %s", obj);
int responseCode = con.getResponseCode();
log.debugf("Response Code : %s", responseCode);
Map<String, List<String>> headers = con.getHeaderFields();
headers.forEach((k, v) -> {
if (k != null) {
responseHeaders.put(k, v);
}
});
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return response.toString();
}
protected Set<String> getServerHosts() {
return adminHandler.getHostsPerServer().values().stream().map(hosts -> {
return selector.selectHost(hosts.toArray(new String[hosts.size()]));
}).filter(host -> host != null)
.collect(Collectors.toSet());
}
protected boolean supportAdvancedAggregate() {
return true;
}
static class RoundRobinHostSelector {
private final AtomicInteger currentHost = new AtomicInteger(0);
public String selectHost(String[] availableHosts) {
if (availableHosts.length == 0) {
return null;
}
int hostIndex = currentHost.incrementAndGet() % availableHosts.length;
return availableHosts[hostIndex];
}
}
}