blob: 702c848a585f412348a0dd9398e911bb8c312cfa [file] [log] [blame]
package org.eclipse.basyx.wrapper.provider;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.StringJoiner;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.basyx.vab.exception.provider.MalformedRequestException;
import org.eclipse.basyx.vab.exception.provider.ProviderException;
import org.eclipse.basyx.vab.modelprovider.VABPathTools;
import org.eclipse.basyx.vab.protocol.http.server.BasysHTTPServlet;
import org.eclipse.basyx.vab.protocol.http.server.ExceptionToHTTPCodeMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.ByteSource;
/**
* Implements a generic HTTP interface based on a JSONProvider that implements
* GET, PUT, POST, PATCH and DELETE and supports returning objects for each of the
* primitive. For PUT, POST and PATCH, it also supports passing objects.
*
* @author espen
*
*/
public class GenericHTTPInterface extends BasysHTTPServlet {
private static Logger logger = LoggerFactory.getLogger(GenericHTTPInterface.class);
private static final String CHARSET = "UTF-8";
private static final String MIMETYPE = "application/json";
/**
* Version information to identify the version of serialized instances
*/
private static final long serialVersionUID = 1L;
private final JSONProvider provider;
/**
* Constructor
*/
public GenericHTTPInterface(IWrapperProvider backend) {
this.provider = new JSONProvider(backend);
}
/**
* Implement "Get" operation
*
* Process HTTP get request - get sub model property value
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String path = extractPath(req);
prepareResponse(resp);
provider.processGet(path, resp.getOutputStream());
} catch (IOException e) {
logger.warn("Exception in HTTP-GET", e);
} catch (ProviderException e) {
int httpCode = ExceptionToHTTPCodeMapper.mapFromException(e);
resp.setStatus(httpCode);
logger.debug("Exception in GET request - response-code: " + httpCode, e);
}
}
/**
* Implement "Set" operation
*/
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String path = extractPath(req);
String serValue = extractSerializedValue(req);
prepareResponse(resp);
provider.processPut(path, serValue, resp.getOutputStream());
} catch (IOException e) {
logger.warn("Exception in HTTP-PUT", e);
} catch (ProviderException e) {
int httpCode = ExceptionToHTTPCodeMapper.mapFromException(e);
resp.setStatus(httpCode);
logger.debug("Exception in PUT request - response-code: " + httpCode, e);
}
}
/**
* <pre>
* Handle HTTP POST operation. Creates a new Property, Operation, Event,
* Submodel or AAS or invokes an operation.
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String path = extractPath(req);
String serValue = extractSerializedValue(req);
prepareResponse(resp);
provider.processPost(path, serValue, resp.getOutputStream());
} catch (IOException e) {
logger.warn("Exception in HTTP-POST", e);
} catch (ProviderException e) {
int httpCode = ExceptionToHTTPCodeMapper.mapFromException(e);
resp.setStatus(httpCode);
logger.debug("Exception in POST request - response-code: " + httpCode, e);
}
}
/**
* Handle a HTTP PATCH operation. Updates a map or collection
*
*/
@Override
protected void doPatch(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String path = extractPath(req);
String serValue = extractSerializedValue(req);
prepareResponse(resp);
provider.processPatch(path, serValue, resp.getOutputStream());
} catch (IOException e) {
logger.warn("Exception in HTTP-PATCH", e);
} catch (ProviderException e) {
int httpCode = ExceptionToHTTPCodeMapper.mapFromException(e);
resp.setStatus(httpCode);
logger.debug("Exception in PATCH request - response-code: " + httpCode, e);
}
}
/**
* Implement "Delete" operation. Deletes any resource under the given path.
*/
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) {
try {
String path = extractPath(req);
prepareResponse(resp);
provider.processDelete(path, resp.getOutputStream());
} catch (IOException e) {
logger.warn("Exception in HTTP-DELETE", e);
} catch (ProviderException e) {
int httpCode = ExceptionToHTTPCodeMapper.mapFromException(e);
resp.setStatus(httpCode);
logger.debug("Exception in DELETE request - response-code: " + httpCode, e);
}
}
private String extractPath(HttpServletRequest req) {
// Extract path
String uri = req.getRequestURI();
// Normalizes URI
String nUri = "/" + VABPathTools.stripSlashes(uri);
String contextPath = req.getContextPath();
if (nUri.startsWith(contextPath) && nUri.length() > getEnvironmentPathSize(req) - 1) {
String path = nUri.substring(getEnvironmentPathSize(req));
String extractedParameters = extractParameters(req);
path = VABPathTools.stripSlashes(path);
if (extractedParameters.isEmpty()) {
path += "/";
} else {
path += extractedParameters;
}
return path;
}
throw new MalformedRequestException("The passed path " + uri + " is not a possbile path for this server.");
}
/**
* Extracts request parameters from the request
*
* @param req
* @return
*/
private String extractParameters(HttpServletRequest req) {
Enumeration<String> parameterNames = req.getParameterNames();
// Collect list of parameters
List<String> parameters = new ArrayList<>();
while (parameterNames.hasMoreElements()) {
StringBuilder ret = new StringBuilder();
String paramName = parameterNames.nextElement();
ret.append(paramName);
ret.append("=");
String[] paramValues = req.getParameterValues(paramName);
for (int i = 0; i < paramValues.length; i++) {
ret.append(paramValues[i]);
}
parameters.add(ret.toString());
}
// If no parameter is existing, return an empty string. Else join the parameters
// and return them prefixed with a ?
if (parameters.isEmpty()) {
return "";
} else {
StringJoiner joiner = new StringJoiner("&");
parameters.stream().forEach(joiner::add);
return "?" + joiner.toString();
}
}
private int getEnvironmentPathSize(HttpServletRequest req) {
return req.getContextPath().length() + req.getServletPath().length();
}
/**
* Read serialized value
* @param req
* @return
* @throws IOException
*/
private String extractSerializedValue(HttpServletRequest req) throws IOException {
// https://www.baeldung.com/convert-input-stream-to-string#guava
ByteSource byteSource = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
return req.getInputStream();
}
};
return byteSource.asCharSource(StandardCharsets.UTF_8).read();
}
private void prepareResponse(HttpServletResponse resp) {
resp.setContentType(MIMETYPE);
resp.setCharacterEncoding(CHARSET);
resp.setStatus(200);
}
}