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 other logback
031 * 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
118                    .getObject(CoreConstants.PATTERN_RULE_REGISTRY);
119            if (contextMap != null) {
120                effectiveMap.putAll(contextMap);
121            }
122        }
123        return effectiveMap;
124    }
125
126    /**
127     * The <b>Title </b> option takes a String value. This option sets the document
128     * title of the generated HTML document.
129     * 
130     * <p>
131     * Defaults to 'Logback Log Messages'.
132     */
133    public void setTitle(String title) {
134        this.title = title;
135    }
136
137    /**
138     * Returns the current value of the <b>Title </b> option.
139     */
140    public String getTitle() {
141        return title;
142    }
143
144    /**
145     * Returns the content type output by this layout, i.e "text/html".
146     */
147    @Override
148    public String getContentType() {
149        return "text/html";
150    }
151
152    /**
153     * Returns appropriate HTML headers.
154     */
155    @Override
156    public String getFileHeader() {
157        StringBuilder sbuf = new StringBuilder();
158        sbuf.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"");
159        sbuf.append(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
160        sbuf.append(LINE_SEPARATOR);
161        sbuf.append("<html>");
162        sbuf.append(LINE_SEPARATOR);
163        sbuf.append("  <head>");
164        sbuf.append(LINE_SEPARATOR);
165        sbuf.append("    <title>");
166        sbuf.append(title);
167        sbuf.append("</title>");
168        sbuf.append(LINE_SEPARATOR);
169
170        cssBuilder.addCss(sbuf);
171
172        sbuf.append(LINE_SEPARATOR);
173        sbuf.append("  </head>");
174        sbuf.append(LINE_SEPARATOR);
175        sbuf.append("<body>");
176        sbuf.append(LINE_SEPARATOR);
177
178        return sbuf.toString();
179    }
180
181    public String getPresentationHeader() {
182        StringBuilder sbuf = new StringBuilder();
183        sbuf.append("<hr/>");
184        sbuf.append(LINE_SEPARATOR);
185        sbuf.append("<p>Log session start time ");
186        sbuf.append(new java.util.Date());
187        sbuf.append("</p><p></p>");
188        sbuf.append(LINE_SEPARATOR);
189        sbuf.append(LINE_SEPARATOR);
190        sbuf.append("<table cellspacing=\"0\">");
191        sbuf.append(LINE_SEPARATOR);
192
193        buildHeaderRowForTable(sbuf);
194
195        return sbuf.toString();
196    }
197
198    private void buildHeaderRowForTable(StringBuilder sbuf) {
199        Converter<E> c = head;
200        String name;
201        sbuf.append("<tr class=\"header\">");
202        sbuf.append(LINE_SEPARATOR);
203        while (c != null) {
204            name = computeConverterName(c);
205            if (name == null) {
206                c = c.getNext();
207                continue;
208            }
209            sbuf.append("<td class=\"");
210            sbuf.append(computeConverterName(c));
211            sbuf.append("\">");
212            sbuf.append(computeConverterName(c));
213            sbuf.append("</td>");
214            sbuf.append(LINE_SEPARATOR);
215            c = c.getNext();
216        }
217        sbuf.append("</tr>");
218        sbuf.append(LINE_SEPARATOR);
219    }
220
221    public String getPresentationFooter() {
222        StringBuilder sbuf = new StringBuilder();
223        sbuf.append("</table>");
224        return sbuf.toString();
225    }
226
227    /**
228     * Returns the appropriate HTML footers.
229     */
230    @Override
231    public String getFileFooter() {
232        StringBuilder sbuf = new StringBuilder();
233        sbuf.append(LINE_SEPARATOR);
234        sbuf.append("</body></html>");
235        return sbuf.toString();
236    }
237
238    protected void startNewTableIfLimitReached(StringBuilder sbuf) {
239        if (this.counter >= CoreConstants.TABLE_ROW_LIMIT) {
240            counter = 0;
241            sbuf.append("</table>");
242            sbuf.append(LINE_SEPARATOR);
243            sbuf.append("<p></p>");
244            sbuf.append("<table cellspacing=\"0\">");
245            sbuf.append(LINE_SEPARATOR);
246            buildHeaderRowForTable(sbuf);
247        }
248    }
249
250    protected String computeConverterName(Converter<E> c) {
251        String className = c.getClass().getSimpleName();
252        int index = className.indexOf("Converter");
253        if (index == -1) {
254            return className;
255        } else {
256            return className.substring(0, index);
257        }
258    }
259
260}