| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.chemistry.opencmis.server.support.filter; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Scanner; |
| |
| import javax.servlet.Filter; |
| import javax.servlet.FilterChain; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletInputStream; |
| import javax.servlet.ServletOutputStream; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.Cookie; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletRequestWrapper; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.servlet.http.HttpServletResponseWrapper; |
| import javax.xml.transform.OutputKeys; |
| import javax.xml.transform.Source; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.transform.stream.StreamSource; |
| |
| import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; |
| import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper; |
| import org.apache.chemistry.opencmis.commons.impl.IOUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class LoggingFilter implements Filter { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class); |
| private static int requestNo = 0; |
| private String logDir; |
| private boolean prettyPrint = true; |
| private boolean logHeaders = true; |
| private int indent = -1; |
| |
| public void init(FilterConfig cfg) throws ServletException { |
| |
| String val; |
| logDir = cfg.getInitParameter("LogDir"); |
| if (null == logDir || logDir.length() == 0) { |
| logDir = System.getProperty("java.io.tmpdir"); |
| } |
| if (null == logDir || logDir.length() == 0) { |
| logDir = "." + File.separator; |
| } |
| |
| if (!logDir.endsWith(File.separator)) { |
| logDir += File.separator; |
| } |
| |
| val = cfg.getInitParameter("Indent"); |
| if (null != val) { |
| indent = Integer.parseInt(val); |
| } |
| if (indent < 0) { |
| indent = 4; |
| } |
| |
| val = cfg.getInitParameter("PrettyPrint"); |
| if (null != val) { |
| prettyPrint = Boolean.parseBoolean(val); |
| } |
| |
| val = cfg.getInitParameter("LogHeaders"); |
| if (null != val) { |
| logHeaders = Boolean.parseBoolean(val); |
| } |
| } |
| |
| public void destroy() { |
| } |
| |
| public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, |
| ServletException { |
| LOG.debug("Logging filter doFilter"); |
| |
| if (resp instanceof HttpServletResponse && req instanceof HttpServletRequest) { |
| LoggingRequestWrapper logReq = new LoggingRequestWrapper((HttpServletRequest) req); |
| LoggingResponseWrapper logResponse = new LoggingResponseWrapper((HttpServletResponse) resp); |
| |
| chain.doFilter(logReq, logResponse); |
| |
| int reqNo = getNextRequestNumber(); |
| String requestFileName = getRequestFileName(reqNo); |
| String cType = logReq.getContentType(); |
| String xmlRequest = logReq.getPayload(); |
| StringBuilder sb = new StringBuilder(); |
| |
| if (logHeaders) { |
| logHeaders(logReq, sb); |
| } |
| |
| if (xmlRequest == null || xmlRequest.length() == 0) { |
| xmlRequest = ""; |
| } |
| |
| if (prettyPrint && cType != null) { |
| if (cType.startsWith("multipart")) { |
| xmlRequest = processMultipart(cType, xmlRequest); |
| } else if (cType.contains("xml")) { |
| xmlRequest = prettyPrintXml(xmlRequest, indent); |
| } |
| } |
| |
| xmlRequest = sb.toString() + xmlRequest; |
| LOG.debug("Found request: " + requestFileName + ": " + xmlRequest); |
| writeTextToFile(requestFileName, xmlRequest); |
| |
| sb = new StringBuilder(); |
| cType = logResponse.getContentType(); |
| String xmlResponse = logResponse.getPayload(); |
| String responseFileName = getResponseFileName(reqNo); |
| |
| if (logHeaders) { |
| logHeaders(logResponse, req.getProtocol(), sb); |
| } |
| |
| if (xmlResponse == null || xmlResponse.length() == 0) { |
| xmlResponse = ""; |
| } |
| |
| if (prettyPrint && cType != null) { |
| if (cType.startsWith("multipart")) { |
| xmlResponse = processMultipart(cType, xmlResponse); |
| } else if (cType.contains("xml")) { |
| xmlResponse = prettyPrintXml(xmlResponse, indent); |
| } else if (cType.contains("json")) { |
| xmlResponse = prettyPrintJson(xmlResponse, indent); |
| } |
| } |
| |
| xmlResponse = sb.toString() + xmlResponse; |
| LOG.debug("Found response: " + responseFileName + ": " + xmlResponse); |
| writeTextToFile(responseFileName, xmlResponse); |
| } else { |
| chain.doFilter(req, resp); |
| } |
| } |
| |
| private void writeTextToFile(String filename, String content) { |
| PrintWriter pw = null; |
| OutputStreamWriter fw = null; |
| try { |
| fw = new OutputStreamWriter(new FileOutputStream(filename), IOUtils.UTF8); |
| pw = new PrintWriter(fw); |
| |
| Scanner scanner = new Scanner(content); |
| while (scanner.hasNextLine()) { |
| String line = scanner.nextLine(); |
| pw.println(line); |
| } |
| scanner.close(); |
| |
| pw.flush(); |
| } catch (IOException e) { |
| LOG.error(e.getMessage(), e); |
| } finally { |
| IOUtils.closeQuietly(pw); |
| IOUtils.closeQuietly(fw); |
| } |
| } |
| |
| private static String prettyPrintXml(String input, int indent) { |
| try { |
| Source xmlInput = new StreamSource(new StringReader(input)); |
| StringWriter stringWriter = new StringWriter(); |
| StreamResult xmlOutput = new StreamResult(stringWriter); |
| TransformerFactory transformerFactory = TransformerFactory.newInstance(); |
| transformerFactory.setAttribute("indent-number", indent); |
| Transformer transformer = transformerFactory.newTransformer(); |
| transformer.setOutputProperty(OutputKeys.INDENT, "yes"); |
| transformer.transform(xmlInput, xmlOutput); |
| return xmlOutput.getWriter().toString(); |
| } catch (Exception e) { |
| throw new RuntimeException(e); // simple exception handling, please |
| // review it |
| } |
| } |
| |
| private static String prettyPrintJson(String input, int indent) { |
| JsonPrettyPrinter pp = new JsonPrettyPrinter(indent); |
| return pp.prettyPrint(input); |
| } |
| |
| private String processMultipart(String cType, String messageBody) throws IOException { |
| int beginIndex = cType.indexOf("boundary=\"") + 10; |
| int endIndex = cType.indexOf('\"', beginIndex); |
| if (endIndex < 0) { |
| endIndex = cType.length(); |
| } |
| String boundary = "--" + cType.substring(beginIndex, endIndex); |
| LOG.debug("Boundary = " + boundary); |
| BufferedReader in = new BufferedReader(new StringReader(messageBody)); |
| StringBuffer out = new StringBuffer(); |
| String line; |
| ByteArrayOutputStream xmlBodyBuffer = new ByteArrayOutputStream(32 * 1024); |
| boolean boundaryFound; |
| |
| boolean inXmlOrJsonBody = false; |
| boolean inXmlOrJsonPart = false; |
| boolean isXml; |
| while ((line = in.readLine()) != null) { |
| if (inXmlOrJsonPart) { |
| if (line.startsWith("<?xml") || line.startsWith("{")) { |
| inXmlOrJsonBody = true; |
| isXml = line.startsWith("<?xml"); |
| byte[] lienBytes = IOUtils.toUTF8Bytes(line); |
| xmlBodyBuffer.write(lienBytes, 0, lienBytes.length); |
| while (inXmlOrJsonBody) { |
| line = in.readLine(); |
| if (line == null) { |
| break; |
| } |
| boundaryFound = line.startsWith(boundary); |
| if (boundaryFound) { |
| LOG.debug("Leaving XML body: " + line); |
| inXmlOrJsonBody = false; |
| inXmlOrJsonPart = false; |
| if (isXml) { |
| out.append(prettyPrintXml(xmlBodyBuffer.toString(IOUtils.UTF8), indent)); |
| } else { |
| out.append(prettyPrintJson(xmlBodyBuffer.toString(IOUtils.UTF8), indent)); |
| } |
| out.append(line).append('\n'); |
| } else { |
| xmlBodyBuffer.write(lienBytes, 0, lienBytes.length); |
| } |
| } |
| } else { |
| LOG.debug("in XML part is: " + line); |
| out.append(line).append('\n'); |
| } |
| |
| } else { |
| LOG.debug("not in XML part: " + line); |
| out.append(line).append('\n'); |
| boundaryFound = line.startsWith(boundary); |
| if (boundaryFound) { |
| LOG.debug("Boundardy found!"); |
| inXmlOrJsonPart = true; |
| } |
| } |
| } |
| in.close(); |
| LOG.debug("End parsing multipart."); |
| |
| return out.toString(); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| private void logHeaders(LoggingRequestWrapper req, StringBuilder sb) { |
| sb.append(req.getMethod()); |
| sb.append(' '); |
| sb.append(req.getRequestURI()); |
| String queryString = req.getQueryString(); |
| if (null != queryString && queryString.length() > 0) { |
| sb.append('?'); |
| sb.append(queryString); |
| } |
| sb.append(' '); |
| sb.append(req.getProtocol()); |
| sb.append('\n'); |
| Enumeration headerNames = req.getHeaderNames(); |
| while (headerNames.hasMoreElements()) { |
| String headerName = headerNames.nextElement().toString(); |
| headerName = headerName.substring(0, 1).toUpperCase() + headerName.substring(1); |
| sb.append(headerName); |
| sb.append(": "); |
| sb.append(req.getHeader(headerName)); |
| sb.append('\n'); |
| } |
| sb.append('\n'); |
| } |
| |
| private void logHeaders(LoggingResponseWrapper resp, String protocol, StringBuilder sb) { |
| sb.append(protocol); |
| sb.append(' '); |
| sb.append(String.valueOf(resp.getStatus())); |
| sb.append('\n'); |
| Map<String, String> headers = resp.getHeaders(); |
| for (Map.Entry<String, String> header : headers.entrySet()) { |
| sb.append(header.getKey()); |
| sb.append(": "); |
| sb.append(header.getValue()); |
| sb.append('\n'); |
| } |
| sb.append('\n'); |
| } |
| |
| private String getRequestFileName(int no) { |
| return logDir + String.format("%05d-request.log", no); |
| } |
| |
| private String getResponseFileName(int no) { |
| return logDir + String.format("%05d-response.log", no); |
| } |
| |
| private static synchronized int getNextRequestNumber() { |
| return requestNo++; |
| } |
| |
| private static class LoggingRequestWrapper extends HttpServletRequestWrapper { |
| |
| private LoggingInputStream is; |
| |
| public LoggingRequestWrapper(HttpServletRequest request) throws IOException { |
| super(request); |
| } |
| |
| @Override |
| public ServletInputStream getInputStream() throws IOException { |
| this.is = new LoggingInputStream(super.getInputStream()); |
| return is; |
| } |
| |
| public String getPayload() { |
| return null == is ? "" : is.getPayload(); |
| } |
| } |
| |
| private static class LoggingInputStream extends ServletInputStream { |
| |
| private ByteArrayOutputStream baous = new ByteArrayOutputStream(); |
| private ServletInputStream is; |
| |
| public LoggingInputStream(ServletInputStream is) { |
| super(); |
| this.is = is; |
| } |
| |
| // Since we are not sure which method is used just overwrite all 4 of |
| // them: |
| @Override |
| public int read() throws IOException { |
| int ch = is.read(); |
| if (ch != -1) { |
| baous.write(ch); |
| } |
| return ch; |
| } |
| |
| @Override |
| public int read(byte[] b) throws IOException { |
| int ch = is.read(b); |
| if (ch != -1) { |
| baous.write(b, 0, ch); |
| } |
| return ch; |
| } |
| |
| @Override |
| public int read(byte[] b, int o, int l) throws IOException { |
| int ch = is.read(b, o, l); |
| if (ch != -1) { |
| baous.write(b, o, ch); |
| } |
| return ch; |
| } |
| |
| @Override |
| public int readLine(byte[] b, int o, int l) throws IOException { |
| int ch = is.readLine(b, o, l); |
| if (ch != -1) { |
| baous.write(b, o, ch); |
| } |
| return ch; |
| } |
| |
| public String getPayload() { |
| try { |
| return baous.toString(IOUtils.UTF8); |
| } catch (UnsupportedEncodingException e) { |
| throw new CmisRuntimeException("Unsupported encoding 'UTF-8'!", e); |
| } |
| } |
| } |
| |
| private static class LoggingResponseWrapper extends HttpServletResponseWrapper { |
| |
| private LoggingOutputStream os; |
| private PrintWriter writer; |
| private int statusCode; |
| private Map<String, String> headers = new HashMap<String, String>(); |
| private String encoding; |
| |
| public LoggingResponseWrapper(HttpServletResponse response) throws IOException { |
| super(response); |
| this.os = new LoggingOutputStream(response.getOutputStream()); |
| } |
| |
| @Override |
| public PrintWriter getWriter() { |
| try { |
| if (null == writer) { |
| writer = new PrintWriter(new OutputStreamWriter(this.getOutputStream(), IOUtils.UTF8)); |
| } |
| return writer; |
| } catch (IOException e) { |
| LOG.error("Failed to get PrintWriter in LoggingFilter: " + e, e); |
| return null; |
| } |
| } |
| |
| @Override |
| public ServletOutputStream getOutputStream() throws IOException { |
| return os; |
| } |
| |
| public String getPayload() { |
| return os.getPayload(); |
| } |
| |
| @Override |
| public void addCookie(Cookie cookie) { |
| super.addCookie(cookie); |
| String value; |
| if (headers.containsKey("Cookie")) { |
| value = headers.get("Cookie") + "; " + cookie.toString(); |
| } else { |
| value = cookie.toString(); |
| } |
| headers.put("Cookie", value); |
| } |
| |
| @Override |
| public void setContentType(String type) { |
| super.setContentType(type); |
| if (headers.containsKey("Content-Type")) { |
| String cType = headers.get("Content-Type"); |
| int pos = cType.indexOf(";charset="); |
| if (pos < 0 && encoding != null) { |
| type = cType + ";charset=" + encoding; |
| } else if (pos >= 0) { |
| encoding = null; |
| } |
| } |
| headers.put("Content-Type", type); |
| } |
| |
| @Override |
| public void setCharacterEncoding(java.lang.String charset) { |
| super.setCharacterEncoding(charset); |
| encoding = charset; |
| if (headers.containsKey("Content-Type")) { |
| String cType = headers.get("Content-Type"); |
| int pos = cType.indexOf(";charset="); |
| if (pos >= 0) { |
| cType = cType.substring(0, pos) + ";charset=" + encoding; |
| } else { |
| cType = cType + ";charset=" + encoding; |
| } |
| headers.put("Content-Type", cType); |
| } |
| } |
| |
| @Override |
| public void setContentLength(int len) { |
| super.setContentLength(len); |
| headers.put("Content-Length", String.valueOf(len)); |
| } |
| |
| private String getDateString(long date) { |
| return DateTimeHelper.formatXmlDateTime(date); |
| } |
| |
| @Override |
| public void setDateHeader(String name, long date) { |
| super.setDateHeader(name, date); |
| headers.put(name, getDateString(date)); |
| } |
| |
| @Override |
| public void addDateHeader(String name, long date) { |
| super.addDateHeader(name, date); |
| if (headers.containsKey(name)) { |
| headers.put(name, headers.get(name) + "; " + getDateString(date)); |
| } else { |
| headers.put(name, getDateString(date)); |
| } |
| } |
| |
| @Override |
| public void setHeader(String name, String value) { |
| super.setHeader(name, value); |
| headers.put(name, value); |
| } |
| |
| @Override |
| public void addHeader(String name, String value) { |
| super.addHeader(name, value); |
| if (headers.containsKey(name)) { |
| headers.put(name, headers.get(name) + "; " + value); |
| } else { |
| headers.put(name, value); |
| } |
| } |
| |
| @Override |
| public void setIntHeader(String name, int value) { |
| super.setIntHeader(name, value); |
| headers.put(name, String.valueOf(value)); |
| } |
| |
| @Override |
| public void addIntHeader(String name, int value) { |
| super.addIntHeader(name, value); |
| if (headers.containsKey(name)) { |
| headers.put(name, headers.get(name) + "; " + value); |
| } else { |
| headers.put(name, String.valueOf(value)); |
| } |
| } |
| |
| @Override |
| public void sendError(int sc) throws IOException { |
| statusCode = sc; |
| super.sendError(sc); |
| } |
| |
| @Override |
| public void sendError(int sc, String msg) throws IOException { |
| statusCode = sc; |
| super.sendError(sc, msg); |
| } |
| |
| @Override |
| public void sendRedirect(String location) throws IOException { |
| statusCode = 302; |
| super.sendRedirect(location); |
| } |
| |
| @Override |
| public void setStatus(int sc) { |
| statusCode = sc; |
| super.setStatus(sc); |
| } |
| |
| public int getStatus() { |
| return statusCode; |
| } |
| |
| public Map<String, String> getHeaders() { |
| return headers; |
| } |
| } |
| |
| private static class LoggingOutputStream extends ServletOutputStream { |
| private ByteArrayOutputStream baous = new ByteArrayOutputStream(); |
| private ServletOutputStream os; |
| |
| public LoggingOutputStream(ServletOutputStream os) { |
| super(); |
| this.os = os; |
| } |
| |
| public String getPayload() { |
| return IOUtils.toUTF8String(baous.toByteArray()); |
| } |
| |
| @Override |
| public void write(byte[] b, int off, int len) { |
| try { |
| baous.write(b, off, len); |
| os.write(b, off, len); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public void write(byte[] b) { |
| try { |
| baous.write(b); |
| os.write(b); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public void write(int ch) throws IOException { |
| baous.write(ch); |
| os.write(ch); |
| } |
| } |
| } |