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