1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.html;
15  
16  import java.util.HashMap;
17  import java.util.Map;
18  import java.util.function.Supplier;
19  
20  import ch.qos.logback.core.Context;
21  import ch.qos.logback.core.CoreConstants;
22  import static ch.qos.logback.core.CoreConstants.LINE_SEPARATOR;
23  import ch.qos.logback.core.LayoutBase;
24  import ch.qos.logback.core.pattern.Converter;
25  import ch.qos.logback.core.pattern.ConverterUtil;
26  import ch.qos.logback.core.pattern.DynamicConverter;
27  import ch.qos.logback.core.pattern.parser.Node;
28  import ch.qos.logback.core.pattern.parser.Parser;
29  import ch.qos.logback.core.spi.ScanException;
30  
31  /**
32   * This class is a base class for HTMLLayout classes part of other logback
33   * modules such as logback-classic and logback-access.
34   * 
35   *
36   * @author Sébastien Pennec
37   */
38  public abstract class HTMLLayoutBase<E> extends LayoutBase<E> {
39  
40      protected String pattern;
41  
42      protected Converter<E> head;
43  
44      protected String title = "Logback Log Messages";
45  
46      // It is the responsibility of derived classes to set
47      // this variable in their constructor to a default value.
48      protected CssBuilder cssBuilder;
49  
50      // counter keeping track of the rows output
51      protected long counter = 0;
52  
53      /**
54       * Set the <b>ConversionPattern </b> option. This is the string which controls
55       * formatting and consists of a mix of literal content and conversion
56       * specifiers.
57       */
58      public void setPattern(String conversionPattern) {
59          pattern = conversionPattern;
60      }
61  
62      /**
63       * Returns the value of the <b>ConversionPattern </b> option.
64       */
65      public String getPattern() {
66          return pattern;
67      }
68  
69      public CssBuilder getCssBuilder() {
70          return cssBuilder;
71      }
72  
73      public void setCssBuilder(CssBuilder cssBuilder) {
74          this.cssBuilder = cssBuilder;
75      }
76  
77      /**
78       * Parses the pattern and creates the Converter linked list.
79       */
80      @Override
81      public void start() {
82          int errorCount = 0;
83  
84          try {
85              Parser<E> p = new Parser<E>(pattern);
86              p.setContext(getContext());
87              Node t = p.parse();
88              this.head = p.compile(t, getEffectiveConverterMap());
89              ConverterUtil.startConverters(this.head);
90          } catch (ScanException ex) {
91              addError("Incorrect pattern found", ex);
92              errorCount++;
93          }
94  
95          if (errorCount == 0) {
96              super.started = true;
97          }
98      }
99  
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 }