/* * 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.Source; import javax.xml.transform.Transformer; 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.apache.chemistry.opencmis.commons.impl.XMLUtils; 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; @Override 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); } } @Override public void destroy() { } @Override 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(1024); 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; if (LOG.isDebugEnabled()) { LOG.debug("Found request: {}: {}", requestFileName, xmlRequest); } writeTextToFile(requestFileName, xmlRequest); sb.setLength(0); 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; if (LOG.isDebugEnabled()) { 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); Transformer transformer = XMLUtils.newTransformer(indent); 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); if (LOG.isDebugEnabled()) { 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) { if (LOG.isDebugEnabled()) { 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 { if (LOG.isDebugEnabled()) { LOG.debug("in XML part is: {}", line); } out.append(line).append('\n'); } } else { if (LOG.isDebugEnabled()) { 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 final 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.toString(), 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); } } }