| package org.eclipse.basyx.vab.protocol.http.server; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.UnsupportedEncodingException; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletInputStream; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.basyx.vab.coder.json.provider.JSONProvider; |
| 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.modelprovider.api.IModelProvider; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * VAB provider class that enables access to an IModelProvider via HTTP REST |
| * interface<br /> |
| * <br /> |
| * REST http interface is as following: <br /> |
| * - GET /aas/submodels/{subModelId} Retrieves submodel with ID {subModelId} |
| * <br /> |
| * - GET /aas/submodels/{subModelId}/properties/a Retrieve property a of |
| * submodel {subModelId}<br /> |
| * - GET /aas/submodels/{subModelId}/properties/a/b Retrieve property a/b of |
| * submodel {subModelId} <br /> |
| * - POST /aas/submodels/{subModelId}/operations/a Invoke operation a of |
| * submodel {subModelId}<br /> |
| * - POST /aas/submodels/{subModelId}/operations/a/b Invoke operation a/b of |
| * submodel {subModelId} |
| * |
| * @author kuhn |
| * |
| */ |
| public class VABHTTPInterface<ModelProvider extends IModelProvider> extends BasysHTTPServlet { |
| |
| private static Logger logger = LoggerFactory.getLogger(VABHTTPInterface.class); |
| |
| /** |
| * Version information to identify the version of serialized instances |
| */ |
| private static final long serialVersionUID = 1L; |
| |
| |
| /** |
| * Reference to IModelProvider backend |
| */ |
| protected JSONProvider<ModelProvider> providerBackend = null; |
| |
| |
| |
| /** |
| * Constructor |
| */ |
| public VABHTTPInterface(ModelProvider provider) { |
| // Store provider reference |
| providerBackend = new JSONProvider<ModelProvider>(provider); |
| } |
| |
| |
| /** |
| * Access model provider |
| */ |
| public ModelProvider getModelProvider() { |
| return providerBackend.getBackendReference(); |
| } |
| |
| /** |
| * Send JSON encoded response |
| */ |
| protected void sendJSONResponse(String path, PrintWriter outputStream, Object jsonValue) { |
| // Output result |
| outputStream.write(jsonValue.toString()); // FIXME throws nullpointer exception if jsonValue is null |
| outputStream.flush(); |
| } |
| |
| |
| /** |
| * Implement "Get" operation |
| * |
| * Process HTTP get request - get sub model property value |
| */ |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| PrintWriter responseWriter = resp.getWriter(); |
| |
| try { |
| String path = extractPath(req); |
| |
| // Setup HTML response header |
| resp.setContentType("application/json"); |
| resp.setCharacterEncoding("UTF-8"); |
| |
| resp.setStatus(200); |
| |
| // Process get request |
| providerBackend.processBaSysGet(path, responseWriter); |
| responseWriter.flush(); |
| } catch(ProviderException e) { |
| int httpCode = ExceptionToHTTPCodeMapper.mapException(e); |
| resp.setStatus(httpCode); |
| responseWriter.flush(); |
| logger.error("Exception in HTTP-GET. Response-code: " + httpCode, e); |
| } |
| |
| } |
| |
| |
| /** |
| * Implement "Set" operation |
| */ |
| @Override |
| protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| PrintWriter responseWriter = resp.getWriter(); |
| |
| try { |
| String path = extractPath(req); |
| String serValue = extractSerializedValue(req); |
| logger.trace("DoPut: {}", serValue); |
| |
| resp.setStatus(200); |
| |
| providerBackend.processBaSysSet(path, serValue.toString(), responseWriter); |
| responseWriter.flush(); |
| } catch(ProviderException e) { |
| int httpCode = ExceptionToHTTPCodeMapper.mapException(e); |
| resp.setStatus(httpCode); |
| responseWriter.flush(); |
| logger.error("Exception in HTTP-PUT. 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 { |
| PrintWriter responseWriter = resp.getWriter(); |
| |
| try { |
| String path = extractPath(req); |
| String serValue = extractSerializedValue(req); |
| |
| logger.trace("DoPost: {}", serValue); |
| |
| // Setup HTML response header |
| resp.setStatus(201); |
| resp.setContentType("application/json"); |
| resp.setCharacterEncoding("UTF-8"); |
| |
| // Check if request is for property creation or operation invoke |
| if (VABPathTools.isOperationPath(path)) { |
| // Invoke BaSys VAB 'invoke' primitive |
| |
| providerBackend.processBaSysInvoke(path, serValue, responseWriter); |
| responseWriter.flush(); |
| |
| } else { |
| // Invoke the BaSys 'create' primitive |
| providerBackend.processBaSysCreate(path, serValue, responseWriter); |
| responseWriter.flush(); |
| } |
| } catch (ProviderException e) { |
| int httpCode = ExceptionToHTTPCodeMapper.mapException(e); |
| resp.setStatus(httpCode); |
| responseWriter.flush(); |
| logger.error("Exception in HTTP-POST. 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 { |
| PrintWriter responseWriter = resp.getWriter(); |
| try { |
| String path = extractPath(req); |
| String serValue = extractSerializedValue(req); |
| logger.trace("DoPatch: {}", serValue); |
| |
| resp.setStatus(200); |
| |
| providerBackend.processBaSysDelete(path, serValue, responseWriter); |
| responseWriter.flush(); |
| } catch(ProviderException e) { |
| int httpCode = ExceptionToHTTPCodeMapper.mapException(e); |
| resp.setStatus(httpCode); |
| responseWriter.flush(); |
| logger.error("Exception in HTTP-PATCH. Response-code: " + httpCode, e); |
| } |
| } |
| |
| |
| /** |
| * Implement "Delete" operation. Deletes any resource under the given path. |
| */ |
| @Override |
| protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| PrintWriter responseWriter = resp.getWriter(); |
| |
| try { |
| String path = extractPath(req); |
| |
| // No parameter to read! Provide serialized null |
| String nullParam = ""; |
| |
| resp.setStatus(200); |
| |
| |
| providerBackend.processBaSysDelete(path, nullParam, responseWriter); |
| responseWriter.flush(); |
| } catch(ProviderException e) { |
| int httpCode = ExceptionToHTTPCodeMapper.mapException(e); |
| resp.setStatus(httpCode); |
| responseWriter.flush(); |
| logger.error("Exception in HTTP-DELETE. Response-code: " + httpCode, e); |
| } |
| } |
| |
| |
| private String extractPath(HttpServletRequest req) throws UnsupportedEncodingException { |
| // 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)) { |
| String path = nUri.substring(getEnvironmentPathSize(req) + 1); |
| |
| // Decode URL |
| path = java.net.URLDecoder.decode(path, "UTF-8"); |
| |
| return path; |
| } |
| throw new MalformedRequestException("The passed path " + uri + " is not a possbile path for this server."); |
| } |
| |
| 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 { |
| // Read request body |
| ServletInputStream is = req.getInputStream(); |
| StringBuilder serValue = new StringBuilder(); |
| |
| // This seems kind of slow... |
| while (!is.isFinished()) { |
| serValue.append(String.valueOf((char) (byte) is.read())); |
| } |
| return serValue.toString(); |
| } |
| } |