package org.smartly.packages.http.impl.handlers.remoting; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.smartly.Smartly; import org.smartly.commons.io.BinaryData; import org.smartly.commons.io.serialization.json.JsonSerializer; import org.smartly.commons.logging.Level; import org.smartly.commons.logging.Logger; import org.smartly.commons.logging.util.LoggingUtils; import org.smartly.commons.remoting.rpc.RemoteInvoker; import org.smartly.commons.util.ByteUtils; import org.smartly.commons.util.DateUtils; import org.smartly.commons.util.FormatUtils; import org.smartly.commons.util.StringUtils; import org.smartly.packages.http.impl.util.ServletUtils; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.net.URLDecoder; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * This Handler depends from "smartly_remoting" package. * If "smartly_remoting" is not included you cannot use this remoting handler. * <p/> * Disable this handler or remove declaration from cong file if you don't intend to use it. */ public class SmartlyRemotingHandler extends ContextHandler { private static final String ENDPOINT = "/rest"; private static final String REQ_PARAM_SEP = "&"; private static final String MIMETYPE_JSON = "application/json;charset=UTF-8"; private static final String MIMETYPE_PNG = "image/png"; private static final String MIMETYPE_TEXT = "text/plain;charset=UTF-8"; private static final String MIMETYPE_ZIP = "application/zip"; private static final String FORMAT_JSON = "json"; private static final String FORMAT_XML = "xml"; private static final String FORMAT_BIN = "bin"; public SmartlyRemotingHandler() { super.setContextPath(ENDPOINT); } @Override public void setContextPath(final String contextPath) { super.setContextPath(contextPath); } @Override public void doHandle(final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); this.handleInternal(target, baseRequest, request, response); } // ------------------------------------------------------------------------ // p r i v a t e // ------------------------------------------------------------------------ private Logger getSmartlyLogger() { return LoggingUtils.getLogger(this); } private void handleInternal(final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { final String method = request.getMethod(); if (method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("POST")) { final String s1 = request.getRequestURI(); final String path = request.getPathInfo(); final String endPoint = request.getContextPath(); final String[] tokens = StringUtils.split(path, "/"); final String responseFormat; final String serviceName; final String methodName; if (tokens.length == 2) { responseFormat = FORMAT_JSON; serviceName = tokens[0]; methodName = tokens[1]; } else if (tokens.length == 3) { responseFormat = tokens[0]; serviceName = tokens[1]; methodName = tokens[2]; } else { // invalid request responseFormat = FORMAT_JSON; serviceName = null; methodName = null; } // ready to invoke remote service try { if (StringUtils.hasText(methodName) && StringUtils.hasText(serviceName)) { final Map<String, String> params = this.getParameters(method, request); final RemoteInvoker invoker = new RemoteInvoker(); final Object invokeResponse = invoker.call(endPoint, serviceName, methodName, params); this.writeResponse(response, responseFormat, invokeResponse); } else { throw new Exception("BAD REQUEST FORMAT."); } } catch (Throwable t) { // service not found or execution error this.writeResponse(response, FORMAT_JSON, t); this.getSmartlyLogger().log(Level.SEVERE, FormatUtils.format("ERROR HANDLING SERVICE REQUEST TO '{0}': {1}", path, t)); } } } private Map<String, String> getParameters(final String method, final HttpServletRequest request) { final Map<String, String> result = new LinkedHashMap<String, String>(); if (method.equalsIgnoreCase("GET")) { //-- GET METHOD --// final Map<String, String[]> map = request.getParameterMap(); if (map.size() > 0) { final Set<String> keys = map.keySet(); for (final String key : keys) { final String[] value = map.get(key); if (null != key && key.length() > 0) { result.put(key, value[0]); } else { result.put(key, ""); } } } } else { //-- POST METHOD --// try { final InputStream is = request.getInputStream(); final byte[] bytes = ByteUtils.getBytes(is); if (null != bytes) { final String data = new String(bytes, Smartly.getCharset()); if (StringUtils.hasLength(data)) { final String[] queryTokens = StringUtils.split(data, REQ_PARAM_SEP); for (final String qt : queryTokens) { final String[] keyValue = StringUtils.split(qt, "="); if (keyValue.length == 2) { result.put(keyValue[0], this.decode(keyValue[1])); } else { result.put(keyValue[0], ""); } } } } is.close(); } catch (Throwable ignored) { } } return result; } private String decode(final String value) { try { return URLDecoder.decode(value, Smartly.getCharset()); } catch (Exception ex) { return value; } } private void writeResponse(final HttpServletResponse response, final String format, final Object data) { try { if (FORMAT_JSON.equalsIgnoreCase(format)) { final String serialized = JsonSerializer.serialize(data); final byte[] bytes = serialized.getBytes(Smartly.getCharset()); ServletUtils.writeResponse(response, DateUtils.now().getTime(), MIMETYPE_JSON, bytes); } else if (FORMAT_XML.equalsIgnoreCase(format)) { // not supported yet } else if (FORMAT_BIN.equalsIgnoreCase(format)) { if (data instanceof BinaryData) { final BinaryData bin_data = (BinaryData) data; final byte[] bytes = bin_data.getBytes(); ServletUtils.writeResponse(response, DateUtils.now().getTime(), bin_data.getMimetype(), bytes); } else if (ByteUtils.isByteArray(data)) { final byte[] bytes = (byte[]) data; ServletUtils.writeResponse(response, DateUtils.now().getTime(), MIMETYPE_PNG, bytes); } else if (data instanceof String) { final byte[] bytes = ((String) data).getBytes(Smartly.getCharset()); ServletUtils.writeResponse(response, DateUtils.now().getTime(), MIMETYPE_TEXT, bytes); } else if (data instanceof InputStream) { final byte[] bytes = ByteUtils.getBytes((InputStream) data); ServletUtils.writeResponse(response, DateUtils.now().getTime(), MIMETYPE_ZIP, bytes); } else { // data not supported } } else { this.writeResponse(response, FORMAT_JSON, new Exception( FormatUtils.format("Unsupported format: '{0}'", format))); } } catch (Throwable t) { this.getSmartlyLogger().log(Level.SEVERE, FormatUtils.format("ERROR WRITING RESPONSE: {0}", t), t); } } // -------------------------------------------------------------------- // p r i v a t e // -------------------------------------------------------------------- // ------------------------------------------------------------------------ // c l a s s - p r i v a t e // ------------------------------------------------------------------------ private class Listener implements AsyncListener { private final Logger _logger; private final String _uri; private Listener(final Logger logger, final String uri) { _logger = logger; _uri = uri; } @Override public void onComplete(AsyncEvent event) throws IOException { ; } @Override public void onTimeout(AsyncEvent event) throws IOException { _logger.log(Level.WARNING, FormatUtils.format("Request time out for '{0}'", _uri)); } @Override public void onError(AsyncEvent event) throws IOException { } @Override public void onStartAsync(AsyncEvent event) throws IOException { } } }