Please wait. This can take some minutes ...
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.
org.cricketmsf.in.http.HttpPortedAdapter Maven / Gradle / Ivy
/*
* Copyright 2020 Grzegorz Skorupa .
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.cricketmsf.in.http;
import org.cricketmsf.Event;
import org.cricketmsf.RequestObject;
import org.cricketmsf.Kernel;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.Map;
import org.cricketmsf.annotation.HttpAdapterHook;
import org.cricketmsf.in.InboundAdapter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.cricketmsf.Adapter;
import org.cricketmsf.Stopwatch;
import org.cricketmsf.event.ProcedureCall;
import org.cricketmsf.in.InboundAdapterIface;
import org.cricketmsf.in.openapi.Operation;
import org.cricketmsf.in.openapi.Parameter;
import org.cricketmsf.in.openapi.ParameterLocation;
public abstract class HttpPortedAdapter
extends InboundAdapter
implements Adapter, HttpAdapterIface, HttpHandler, InboundAdapterIface/*, org.eclipse.jetty.server.Handler*/ {
public final static String JSON = "application/json";
public final static String XML = "text/xml";
public final static String CSV = "text/csv";
public final static String HTML = "text/html";
public final static String TEXT = "text/plain";
public final static int SERVICE_MODE = 0;
public final static int WEBSITE_MODE = 1;
private final String[] acceptedTypes = {
"application/json",
"text/xml",
"text/html",
"text/csv",
"text/plain"
};
protected HashMap acceptedTypesMap;
private String context;
private boolean extendedResponse = false;
SimpleDateFormat dateFormat;
protected int mode = SERVICE_MODE;
protected HashMap operations = new HashMap();
public HttpPortedAdapter() {
super();
try {
acceptedTypesMap = new HashMap<>();
for (String acceptedType : acceptedTypes) {
acceptedTypesMap.put(acceptedType, acceptedType);
}
dateFormat = Kernel.getInstance().dateFormat;
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void loadProperties(HashMap properties, String adapterName) {
super.loadProperties(properties, adapterName);
setContext(properties.get("context"));
}
@Override
protected void getServiceHooks(String adapterName) {
HttpAdapterHook ah;
String requestMethod;
// for every method of a Kernel instance (our service class extending Kernel)
for (Method m : Kernel.getInstance().getClass().getMethods()) {
ah = (HttpAdapterHook) m.getAnnotation(HttpAdapterHook.class);
// we search for annotated method
if (ah != null) {
requestMethod = ah.requestMethod();
if (ah.adapterName().equals(adapterName)) {
addHookMethodNameForMethod(requestMethod, m.getName());
}
}
}
}
@Override
public Object handleInput(Object input) {
if (input instanceof HttpExchange) {
try {
handle((HttpExchange) input);
} catch (IOException ex) {
Kernel.getInstance().dispatchEvent(Event.logWarning(this, ex.getMessage()));
}
}
return null;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
long rootEventId = Kernel.getEventId();
try {
Stopwatch timer = null;
if (Kernel.getInstance().isFineLevel()) {
timer = new Stopwatch();
}
String acceptedResponseType = JSON;
try {
acceptedResponseType
= acceptedTypesMap.getOrDefault(exchange.getRequestHeaders().get("Accept").get(0), JSON);
} catch (Exception e) {
}
// cerating Result object
RequestObject requestObject = buildRequestObject(exchange, acceptedResponseType);
if (null != properties.get("dump-request") && "true".equalsIgnoreCase(properties.get("dump-request"))) {
Kernel.getInstance().getLogger().print(dumpRequest(requestObject));
}
Result result = createResponse(requestObject, rootEventId);
acceptedResponseType = setResponseType(acceptedResponseType, result.getFileExtension());
//set content type and print response to string format as JSON if needed
Headers headers = exchange.getResponseHeaders();
byte[] responseData;
Iterator it = result.getHeaders().keySet().iterator();
String key;
while (it.hasNext()) {
key = (String) it.next();
List values = result.getHeaders().get(key);
for (int i = 0; i < values.size(); i++) {
headers.set(key, values.get(i));
}
}
switch (result.getCode()) {
case ResponseCode.MOVED_PERMANENTLY:
case ResponseCode.MOVED_TEMPORARY:
if (!headers.containsKey("Location")) {
String newLocation = "/";
if(result.getData() != null){
newLocation=""+result.getData();
}else if(result.getMessage()!=null){
newLocation=result.getMessage();
}
headers.set("Location", newLocation);
responseData = ("moved to ".concat(newLocation)).getBytes("UTF-8");
} else {
responseData = "".getBytes();
}
break;
case ResponseCode.NOT_FOUND:
headers.set("Content-type", "text/html");
responseData = result.getPayload();
break;
default:
if (!headers.containsKey("Content-type")) {
if (acceptedTypesMap.containsKey(acceptedResponseType)) {
headers.set("Content-type", acceptedResponseType.concat("; charset=UTF-8"));
responseData = formatResponse(acceptedResponseType, result);
} else {
headers.set("Content-type", getMimeType(result.getFileExtension()));
responseData = result.getPayload();
}
} else {
String rContentType = headers.getFirst("Content-type");
headers.set("Content-type", rContentType.concat("; charset=UTF-8"));
if (acceptedTypesMap.containsKey(rContentType)) {
responseData = formatResponse(rContentType, result);
} else {
responseData = result.getPayload();
}
}
headers.set("Last-Modified", result.getModificationDateFormatted());
//TODO: get max age and no-cache info from the result object
if (result.getMaxAge() > 0) {
headers.set("Cache-Control", "max-age=" + result.getMaxAge()); // 1 hour
} else {
headers.set("Pragma", "no-cache");
}
if (exchange.getRequestMethod().equalsIgnoreCase("OPTIONS")) {
CorsProcessor.getResponseHeaders(headers, exchange.getRequestHeaders(), Kernel.getInstance().getCorsHeaders());
} else if (exchange.getRequestURI().getPath().startsWith("/api/")) { //TODO: this is workaround
CorsProcessor.getResponseHeaders(headers, exchange.getRequestHeaders(), Kernel.getInstance().getCorsHeaders());
} else if (exchange.getRequestURI().getPath().endsWith(".tag")) { //TODO: this is workaround
CorsProcessor.getResponseHeaders(headers, exchange.getRequestHeaders(), Kernel.getInstance().getCorsHeaders());
}
if (result.getCode() == 0) {
result.setCode(ResponseCode.OK);
} else {
if (responseData.length == 0) {
if (result.getMessage() != null) {
responseData = result.getMessage().getBytes("UTF-8");
}
}
}
break;
}
//TODO: format logs to have clear info about root event id
if (Kernel.getInstance().isFineLevel()) {
Kernel.getInstance().dispatchEvent(
Event.logFinest("HttpAdapter", "event " + rootEventId + " processing takes " + timer.time(TimeUnit.MILLISECONDS) + "ms")
);
}
if (responseData.length > 0) {
exchange.sendResponseHeaders(result.getCode(), responseData.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(responseData);
}
} else {
exchange.sendResponseHeaders(result.getCode(), -1);
}
sendLogEvent(exchange, responseData.length);
result = null;
} catch (IOException e) {
//e.printStackTrace();
Kernel.getInstance().dispatchEvent(Event.logWarning(this, exchange.getRequestURI().getPath() + " " + e.getMessage()));
}
exchange.close();
}
private String getMimeType(String fileExt) {
if (null == fileExt) {
return TEXT;
}
switch (fileExt.toLowerCase()) {
case ".ico":
return "image/x-icon";
case ".jpg":
return "image/jpg";
case ".jpeg":
return "image/jpeg";
case ".gif":
return "image/gif";
case ".png":
return "image/png";
case ".css":
return "text/css";
case ".csv":
return "text/csv";
case ".js":
return "text/javascript";
case ".svg":
return "image/svg+xml";
case ".htm":
case ".html":
return "text/html; charset=utf-8";
case ".json":
return JSON;
default:
return TEXT;
}
}
/**
* Calculates response type based on the file type
*
* @param acceptedResponseType accepted mime type
* @param fileExt file extension
* @return response type
*/
protected String setResponseType(String acceptedResponseType, String fileExt) {
//return fileExt != null ? expectedResponseType : NONE;
return acceptedResponseType;
}
public byte[] formatResponse(String type, Result result) {
byte[] r = {};
String formattedResponse;
switch (type) {
case JSON:
formattedResponse = JsonFormatter.getInstance().format(true, isExtendedResponse() ? result : result.getData());
break;
case XML:
//TODO: extended response is not possible because of "java.util.List is an interface, and JAXB can't handle interfaces"
formattedResponse = XmlFormatter.getInstance().format(true, result.getData());
break;
case CSV:
// formats only Result.getData() object
formattedResponse = CsvFormatter.getInstance().format(result);
break;
case TEXT:
// formats only Result.getData() object
formattedResponse = TxtFormatter.getInstance().format(result);
break;
default:
formattedResponse = JsonFormatter.getInstance().format(true, result);
break;
}
try {
r = formattedResponse.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
//e.printStackTrace();
Kernel.getInstance().dispatchEvent(Event.logSevere("HttpAdapter.formatResponse()", e.getClass().getSimpleName() + " " + e.getMessage()));
}
return r;
}
RequestObject buildRequestObject(HttpExchange exchange, String acceptedResponseType) {
// Remember that "parameters" attribute is created by filter
Map parameters = (Map) exchange.getAttribute("parameters");
String method = exchange.getRequestMethod();
String pathExt = exchange.getRequestURI().getPath();
if (null != pathExt) {
pathExt = pathExt.substring(exchange.getHttpContext().getPath().length());
while (pathExt.startsWith("/")) {
pathExt = pathExt.substring(1);
}
}
RequestObject requestObject = new RequestObject();
requestObject.method = method;
requestObject.parameters = parameters;
requestObject.uri = exchange.getRequestURI().toString();
requestObject.pathExt = pathExt;
requestObject.headers = exchange.getRequestHeaders();
requestObject.clientIp = exchange.getRemoteAddress().getAddress().getHostAddress();
requestObject.acceptedResponseType = acceptedResponseType;
requestObject.body = (String) exchange.getAttribute("body");
if (null == requestObject.body) {
requestObject.body = "";
}
return requestObject;
}
protected abstract ProcedureCall preprocess(RequestObject request, long rootEventId);
protected Result postprocess(Result result) {
return result;
}
private Result createResponse(RequestObject requestObject, long rootEventId) {
String methodName = null;
Result result = new StandardResult();
if (mode == WEBSITE_MODE) {
if (!requestObject.uri.endsWith("/")) {
if (requestObject.uri.lastIndexOf("/") > requestObject.uri.lastIndexOf(".")) {
// redirect to index.file but only if property index.file is not null
result.setCode(ResponseCode.MOVED_PERMANENTLY);
result.setMessage(requestObject.uri.concat("/"));
return result;
}
}
}
try {
ProcedureCall pCall = preprocess(requestObject, Kernel.getEventId());
if (pCall.requestHandled) { // request processed by the adapter
if (pCall.responseCode < 100 || pCall.responseCode > 1000) {
result.setCode(ResponseCode.BAD_REQUEST);
} else {
result.setCode(pCall.responseCode);
}
result.setData(pCall.response);
result.setHeader("Content-type", pCall.contentType);
} else { // pCall must be processed by the Kernel
sendLogEvent(Event.LOG_FINE, "sending request to hook method " + pCall.procedureName + "@" + pCall.event.getClass().getSimpleName());
result = (Result) Kernel.getInstance().getEventProcessingResult(
pCall.event,
pCall.procedureName
);
if (null != result) {
if (pCall.responseCode != 0) {
result.setCode(pCall.responseCode);
} else {
result = postprocess(result);
if (null!=result && (result.getCode() < 100 || result.getCode() > 1000)) {
result.setCode(ResponseCode.BAD_REQUEST);
}
}
}
}
} catch (ClassCastException e) {
sendLogEvent(Event.LOG_SEVERE, "class cast exception");
result.setCode(ResponseCode.INTERNAL_SERVER_ERROR);
result.setMessage("handler method error");
result.setFileExtension(null);
} catch (Exception e) {
e.printStackTrace();
}
if (null == result) {
result = new StandardResult("null result returned by the service");
result.setCode(ResponseCode.INTERNAL_SERVER_ERROR);
}
return result;
}
/**
* @param context the context to set
*/
public void setContext(String context) {
this.context = context;
}
public String getContext() {
return context;
}
@Override
public void addHookMethodNameForMethod(String requestMethod, String hookMethodName) {
hookMethodNames.put(requestMethod, hookMethodName);
}
@Override
public String getHookMethodNameForMethod(String requestMethod) {
String result = null;
if ("HEAD".equalsIgnoreCase(requestMethod)) {
result = hookMethodNames.get("GET");
} else {
result = hookMethodNames.get(requestMethod);
}
if (null == result) {
result = hookMethodNames.get("*");
}
return result;
}
protected void sendLogEvent(HttpExchange exchange, int length) {
StringBuilder sb = new StringBuilder();
sb.append(exchange.getRemoteAddress().getAddress().getHostAddress());
sb.append(" - ");
try {
sb.append(exchange.getPrincipal().getUsername());
} catch (Exception e) {
sb.append("-");
}
sb.append(" ");
sb.append(dateFormat.format(new Date()));
sb.append(" ");
sb.append(exchange.getRequestMethod());
sb.append(" ");
sb.append(exchange.getProtocol());
sb.append(" ");
sb.append(exchange.getRequestURI());
sb.append(" ");
sb.append(exchange.getResponseCode());
sb.append(" ");
sb.append(length);
Event event = new Event(
"HttpAdapter",
Event.CATEGORY_HTTP_LOG,
Event.LOG_INFO,
null,
sb.toString());
Kernel.getInstance().dispatchEvent(event);
}
protected void sendLogEvent(String type, String message) {
Event event = new Event(
"HttpAdapter",
Event.CATEGORY_LOG,
type,
null,
message);
Kernel.getInstance().dispatchEvent(event);
}
protected void sendLogEvent(String message) {
sendLogEvent(Event.LOG_INFO, message);
}
/**
* @return the extendedResponse
*/
public boolean isExtendedResponse() {
return extendedResponse;
}
/**
* @param paramValue value
*/
public void setExtendedResponse(String paramValue) {
this.extendedResponse = !("false".equalsIgnoreCase(paramValue));
}
public void setDateFormat(String dateFormat) {
if (dateFormat != null) {
this.dateFormat = new SimpleDateFormat(dateFormat);
}
}
public DateFormat getDateFormat() {
return dateFormat;
}
public static String dumpRequest(RequestObject req) {
StringBuilder sb = new StringBuilder();
sb.append("************** REQUEST ****************").append("\r\n");
sb.append("URI:").append(req.uri).append("\r\n");
sb.append("PATHEXT:").append(req.pathExt).append("\r\n");
sb.append("METHOD:").append(req.method).append("\r\n");
sb.append("ACCEPT:").append(req.acceptedResponseType).append("\r\n");
sb.append("CLIENT IP:").append(req.clientIp).append("\r\n");
sb.append("***BODY:").append("\r\n");
sb.append(req.body).append("\r\n");
sb.append("***BODY.").append("\r\n");
sb.append("***HEADERS:").append("\r\n");
req.headers.keySet().forEach(key -> {
sb.append(key)
.append(":")
.append(req.headers.getFirst(key))
.append("\r\n");
});
sb.append("***HEADERS.").append("\r\n");
sb.append("***PARAMETERS:").append("\r\n");
req.parameters.keySet().forEach(key -> {
sb.append(key)
.append(":")
.append(req.parameters.get(key))
.append("\r\n");
});
sb.append("***PARAMETERS.").append("\r\n");
return sb.toString();
}
@Override
public final void addOperationConfig(Operation operation) {
System.out.println(">>> "+getName()+" adding operation "+operation.getMethod()+" "+operation.getParameters().size());
operations.put(operation.getMethod(), operation);
}
protected final ArrayList getParams(String method, boolean required, ParameterLocation location) {
ArrayList params = new ArrayList();
try {
getOperations().get(method).getParameters().forEach(param -> {
if (required == param.isRequired() && location==param.getIn()) {
params.add(param);
}
});
} catch (NullPointerException e) {
e.printStackTrace();
}
return params;
}
protected int getParamIndex(ArrayList params, String name) {
for (int i = 0; i < params.size(); i++) {
if (params.get(i).getName().equals(name)) {
return i;
}
}
return -1;
}
/**
* This method is called while generating OpenAPI specification for the
* adapter.
*
* @return map of declared operations
*/
@Override
public final Map getOperations() {
return operations;
}
/**
* Can be overriden to provide OpenAPI specification of the adapter class.
*/
@Override
public void defineApi() {
}
}