001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.html;
015
016import java.util.HashMap;
017import java.util.Map;
018
019import ch.qos.logback.core.Context;
020import ch.qos.logback.core.CoreConstants;
021import static ch.qos.logback.core.CoreConstants.LINE_SEPARATOR;
022import ch.qos.logback.core.LayoutBase;
023import ch.qos.logback.core.pattern.Converter;
024import ch.qos.logback.core.pattern.ConverterUtil;
025import ch.qos.logback.core.pattern.parser.Node;
026import ch.qos.logback.core.pattern.parser.Parser;
027import ch.qos.logback.core.spi.ScanException;
028
029/**
030 * This class is a base class for HTMLLayout classes part of
031 * other logback modules such as logback-classic and logback-access.
032 * 
033 *
034 * @author Sébastien Pennec
035 */
036public abstract class HTMLLayoutBase<E> extends LayoutBase<E> {
037
038    protected String pattern;
039
040    protected Converter<E> head;
041
042    protected String title = "Logback Log Messages";
043
044    // It is the responsibility of derived classes to set
045    // this variable in their constructor to a default value.
046    protected CssBuilder cssBuilder;
047
048    // counter keeping track of the rows output
049    protected long counter = 0;
050
051    /**
052     * Set the <b>ConversionPattern </b> option. This is the string which controls
053     * formatting and consists of a mix of literal content and conversion
054     * specifiers.
055     */
056    public void setPattern(String conversionPattern) {
057        pattern = conversionPattern;
058    }
059
060    /**
061     * Returns the value of the <b>ConversionPattern </b> option.
062     */
063    public String getPattern() {
064        return pattern;
065    }
066
067    public CssBuilder getCssBuilder() {
068        return cssBuilder;
069    }
070
071    public void setCssBuilder(CssBuilder cssBuilder) {
072        this.cssBuilder = cssBuilder;
073    }
074
075    /**
076     * Parses the pattern and creates the Converter linked list.
077     */
078    @Override
079    public void start() {
080        int errorCount = 0;
081
082        try {
083            Parser<E> p = new Parser<E>(pattern);
084            p.setContext(getContext());
085            Node t = p.parse();
086            this.head = p.compile(t, getEffectiveConverterMap());
087            ConverterUtil.startConverters(this.head);
088        } catch (ScanException ex) {
089            addError("Incorrect pattern found", ex);
090            errorCount++;
091        }
092
093        if (errorCount == 0) {
094            super.started = true;
095        }
096    }
097
098    protected abstract Map<String, String> getDefaultConverterMap();
099
100    /**
101     * Returns a map where the default converter map is merged with the map
102     * contained in the context.
103     */
104    public Map<String, String> getEffectiveConverterMap() {
105        Map<String, String> effectiveMap = new HashMap<String, String>();
106
107        // add the least specific map fist
108        Map<String, String> defaultMap = getDefaultConverterMap();
109        if (defaultMap != null) {
110            effectiveMap.putAll(defaultMap);
111        }
112
113        // contextMap is more specific than the default map
114        Context context = getContext();
115        if (context != null) {
116            @SuppressWarnings("unchecked")
117            Map<String, String> contextMap = (Map<String, String>) context.getObject(CoreConstants.PATTERN_RULE_REGISTRY);
118            if (contextMap != null) {
119                effectiveMap.putAll(contextMap);
120            }
121        }
122        return effectiveMap;
123    }
124
125    /**
126     * The <b>Title </b> option takes a String value. This option sets the
127     * document title of the generated HTML document.
128     * 
129     * <p>
130     * Defaults to 'Logback Log Messages'.
131     */
132    public void setTitle(String title) {
133        this.title = title;
134    }
135
136    /**
137     * Returns the current value of the <b>Title </b> option.
138     */
139    public String getTitle() {
140        return title;
141    }
142
143    /**
144     * Returns the content type output by this layout, i.e "text/html".
145     */
146    @Override
147    public String getContentType() {
148        return "text/html";
149    }
150
151    /**
152     * Returns appropriate HTML headers.
153     */
154    @Override
155    public String getFileHeader() {
156        StringBuilder sbuf = new StringBuilder();
157        sbuf.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"");
158        sbuf.append(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
159        sbuf.append(LINE_SEPARATOR);
160        sbuf.append("<html>");
161        sbuf.append(LINE_SEPARATOR);
162        sbuf.append("  <head>");
163        sbuf.append(LINE_SEPARATOR);
164        sbuf.append("    <title>");
165        sbuf.append(title);
166        sbuf.append("</title>");
167        sbuf.append(LINE_SEPARATOR);
168
169        cssBuilder.addCss(sbuf);
170
171        sbuf.append(LINE_SEPARATOR);
172        sbuf.append("  </head>");
173        sbuf.append(LINE_SEPARATOR);
174        sbuf.append("<body>");
175        sbuf.append(LINE_SEPARATOR);
176
177        return sbuf.toString();
178    }
179
180    public String getPresentationHeader() {
181        StringBuilder sbuf = new StringBuilder();
182        sbuf.append("<hr/>");
183        sbuf.append(LINE_SEPARATOR);
184        sbuf.append("<p>Log session start time ");
185        sbuf.append(new java.util.Date());
186        sbuf.append("</p><p></p>");
187        sbuf.append(LINE_SEPARATOR);
188        sbuf.append(LINE_SEPARATOR);
189        sbuf.append("<table cellspacing=\"0\">");
190        sbuf.append(LINE_SEPARATOR);
191
192        buildHeaderRowForTable(sbuf);
193
194        return sbuf.toString();
195    }
196
197    private void buildHeaderRowForTable(StringBuilder sbuf) {
198        Converter<E> c = head;
199        String name;
200        sbuf.append("<tr class=\"header\">");
201        sbuf.append(LINE_SEPARATOR);
202        while (c != null) {
203            name = computeConverterName(c);
204            if (name == null) {
205                c = c.getNext();
206                continue;
207            }
208            sbuf.append("<td class=\"");
209            sbuf.append(computeConverterName(c));
210            sbuf.append("\">");
211            sbuf.append(computeConverterName(c));
212            sbuf.append("</td>");
213            sbuf.append(LINE_SEPARATOR);
214            c = c.getNext();
215        }
216        sbuf.append("</tr>");
217        sbuf.append(LINE_SEPARATOR);
218    }
219
220    public String getPresentationFooter() {
221        StringBuilder sbuf = new StringBuilder();
222        sbuf.append("</table>");
223        return sbuf.toString();
224    }
225
226    /**
227     * Returns the appropriate HTML footers.
228     */
229    @Override
230    public String getFileFooter() {
231        StringBuilder sbuf = new StringBuilder();
232        sbuf.append(LINE_SEPARATOR);
233        sbuf.append("</body></html>");
234        return sbuf.toString();
235    }
236
237    protected void startNewTableIfLimitReached(StringBuilder sbuf) {
238        if (this.counter >= CoreConstants.TABLE_ROW_LIMIT) {
239            counter = 0;
240            sbuf.append("</table>");
241            sbuf.append(LINE_SEPARATOR);
242            sbuf.append("<p></p>");
243            sbuf.append("<table cellspacing=\"0\">");
244            sbuf.append(LINE_SEPARATOR);
245            buildHeaderRowForTable(sbuf);
246        }
247    }
248
249    protected String computeConverterName(Converter<E> c) {
250        String className = c.getClass().getSimpleName();
251        int index = className.indexOf("Converter");
252        if (index == -1) {
253            return className;
254        } else {
255            return className.substring(0, index);
256        }
257    }
258
259}