View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.webapp;
21  
22  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
23  import org.apache.myfaces.tobago.renderkit.html.HtmlTypes;
24  import org.apache.myfaces.tobago.renderkit.html.MarkupLanguageAttributes;
25  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  import javax.faces.component.UIComponent;
30  import java.io.IOException;
31  import java.io.Writer;
32  import java.net.URI;
33  
34  public abstract class TobagoResponseWriterBase extends TobagoResponseWriter {
35  
36    private static final Logger LOG = LoggerFactory.getLogger(TobagoResponseWriterBase.class);
37  
38    protected static final String XML_VERSION_1_0_ENCODING_UTF_8 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
39  
40    protected static final char[] XML_VERSION_1_0_ENCODING_UTF_8_CHARS = XML_VERSION_1_0_ENCODING_UTF_8.toCharArray();
41  
42    private int level = 0;
43  
44    private int inlineStack = 0;
45  
46    private UIComponent component;
47  
48    private boolean startStillOpen;
49  
50    private final Writer writer;
51  
52    private final String contentType;
53  
54    private final String characterEncoding;
55  
56    protected TobagoResponseWriterBase(final Writer writer, final String contentType, final String characterEncoding) {
57      this.writer = writer;
58      this.contentType = contentType;
59      this.characterEncoding = characterEncoding != null ? characterEncoding : "UTF-8";
60    }
61  
62    protected final Writer getWriter() {
63      return writer;
64    }
65  
66    protected final UIComponent getComponent() {
67      return component;
68    }
69  
70    protected final void setComponent(final UIComponent component) {
71      this.component = component;
72    }
73  
74    protected final boolean isStartStillOpen() {
75      return startStillOpen;
76    }
77  
78    protected final void setStartStillOpen(final boolean startStillOpen) {
79      this.startStillOpen = startStillOpen;
80    }
81  
82    protected final String findValue(final Object value, final String property) {
83      if (value != null) {
84        return value instanceof String ? (String) value : value.toString();
85      } else if (property != null) {
86        if (component != null) {
87          final Object object = component.getAttributes().get(property);
88          if (object != null) {
89            return object instanceof String ? (String) object : object.toString();
90          } else {
91            return null;
92          }
93        } else {
94          final String trace = getCallingClassStackTraceElementString();
95          LOG.warn("Don't know what to do! "
96              + "Property defined, but no component to get a value. (value=null, property='" + property + "') "
97              + trace.substring(trace.indexOf('(')));
98          return null;
99        }
100     } else {
101       final String trace = getCallingClassStackTraceElementString();
102       LOG.warn("Don't know what to do! "
103           + "No value and no property defined. (value=null, property=null)"
104           + trace.substring(trace.indexOf('(')));
105       return null;
106     }
107   }
108 
109   @Override
110   public void write(final char[] cbuf, final int off, final int len)
111       throws IOException {
112     writer.write(cbuf, off, len);
113   }
114 
115   @Override
116   public void write(final String string) throws IOException {
117     writeInternal(writer, string);
118   }
119 
120   protected final void writeInternal(final Writer sink, final String string) throws IOException {
121     closeOpenTag();
122     sink.write(string);
123   }
124 
125   @Override
126   public void write(final int j) throws IOException {
127     closeOpenTag();
128     writer.write(j);
129   }
130 
131   @Override
132   public void write(final char[] chars) throws IOException {
133     closeOpenTag();
134     writer.write(chars);
135   }
136 
137   @Override
138   public void write(final String string, final int j, final int k) throws IOException {
139     closeOpenTag();
140     writer.write(string, j, k);
141   }
142 
143   @Override
144   public void close() throws IOException {
145     closeOpenTag();
146     writer.close();
147   }
148 
149   @Override
150   public void flush() throws IOException {
151     /*
152     From the api:
153     Flush any ouput buffered by the output method to the underlying Writer or OutputStream.
154     This method will not flush the underlying Writer or OutputStream;
155     it simply clears any values buffered by this ResponseWriter.
156      */
157     closeOpenTag();
158   }
159 
160   protected void closeOpenTag() throws IOException {
161     if (startStillOpen) {
162       writer.write('>');
163       startStillOpen = false;
164     }
165   }
166 
167   @Override
168   public void startDocument() throws IOException {
169     // nothing to do
170   }
171 
172   @Override
173   public void endDocument() throws IOException {
174     // nothing to do
175   }
176 
177   @Override
178   public String getContentType() {
179     return contentType;
180   }
181 
182   @Override
183   public String getCharacterEncoding() {
184     return characterEncoding;
185   }
186 
187   @Override
188   public void startElement(final String name, final UIComponent currentComponent) throws IOException {
189     final boolean inline = HtmlElements.isInline(name);
190     if (inline) {
191       inlineStack++;
192     }
193     this.component = currentComponent;
194     startElementInternal(writer, name, HtmlElements.isInline(name));
195   }
196 
197   @Override
198   public void startElement(final HtmlElements name) throws IOException {
199     final boolean inline = name.isInline();
200     if (inline) {
201       inlineStack++;
202     }
203     startElementInternal(writer, name.getValue(), name.isInline());
204     if (!name.isVoid()) {
205       level++;
206     }
207   }
208 
209   protected void startElementInternal(final Writer sink, final String name, final boolean inline)
210       throws IOException {
211     if (startStillOpen) {
212       sink.write('>');
213     }
214     if (inlineStack <= 1) {
215       sink.write('\n');
216       for (int i = 0; i < level; i++) {
217         sink.write(' ');
218       }
219     }
220     sink.write('<');
221     sink.write(name);
222     startStillOpen = true;
223   }
224 
225   @Override
226   public void endElement(final String name) throws IOException {
227     final boolean inline = HtmlElements.isInline(name);
228     if (HtmlElements.isVoid(name)) {
229       closeEmptyTag();
230     } else {
231       endElementInternal(writer, name, inline);
232     }
233     startStillOpen = false;
234     if (inline) {
235       inlineStack--;
236       assert inlineStack >= 0;
237     }
238   }
239 
240   @Override
241   public void endElement(final HtmlElements name) throws IOException {
242     final boolean inline = name.isInline();
243     if (name.isVoid()) {
244       closeEmptyTag();
245     } else {
246       if (!name.isVoid()) {
247         level--;
248       }
249       endElementInternal(writer, name.getValue(), inline);
250     }
251     startStillOpen = false;
252     if (inline) {
253       inlineStack--;
254       assert inlineStack >= 0;
255     }
256   }
257 
258   @Override
259   public void writeComment(final Object obj) throws IOException {
260     closeOpenTag();
261     final String comment = obj.toString();
262     writer.write('\n');
263     for (int i = 0; i < level; i++) {
264       writer.write(' ');
265     }
266     write("<!--");
267     write(comment);
268     write("-->");
269   }
270 
271   /**
272    * @deprecated since 3.0.0
273    */
274   @Override
275   @Deprecated
276   public void writeAttribute(final String name, final Object value, final String property)
277       throws IOException {
278 
279     final String attribute = findValue(value, property);
280     writeAttribute(new MarkupLanguageAttributes() {
281       @Override
282       public String getValue() {
283         return name;
284       }
285     }, attribute, true);
286   }
287 
288   protected final String getCallingClassStackTraceElementString() {
289     final StackTraceElement[] stackTrace = new Exception().getStackTrace();
290     int j = 1;
291     while (stackTrace[j].getClassName().contains("ResponseWriter")) {
292       j++;
293     }
294     return stackTrace[j].toString();
295   }
296 
297   @Override
298   public void writeURIAttribute(final String name, final Object value, final String property)
299       throws IOException {
300     if (value != null) {
301       final URI uri = URI.create(value.toString());
302       writeAttribute(name, uri.toASCIIString(), property);
303     }
304   }
305 
306 // interface TobagoResponseWriter //////////////////////////////////////////////////////////////////////////////////
307 
308   @Override
309   public void writeAttribute(final MarkupLanguageAttributes name, final String value, final boolean escape)
310       throws IOException {
311     writeAttributeInternal(writer, name, value, escape);
312   }
313 
314   @Override
315   public void writeAttribute(final MarkupLanguageAttributes name, final HtmlTypes types) throws IOException {
316     writeAttributeInternal(writer, name, types.getValue(), false);
317   }
318 
319   @Override
320   public void writeURIAttribute(final MarkupLanguageAttributes name, final String value)
321       throws IOException {
322     if (value != null) {
323       final URI uri = URI.create(value);
324       writeAttribute(name, uri.toASCIIString(), true);
325     }
326   }
327 
328   protected void endElementInternal(final Writer sink, final String name, final boolean inline) throws IOException {
329     if (startStillOpen) {
330       sink.write('>');
331     }
332     if (inline) {
333       sink.write("</");
334     } else {
335       sink.write('\n');
336       for (int i = 0; i < level; i++) {
337         sink.write(' ');
338       }
339       sink.write("</");
340     }
341     sink.write(name);
342     sink.write('>');
343   }
344 
345   protected abstract void closeEmptyTag() throws IOException;
346 
347   protected void writeAttributeInternal(
348       final Writer sink, final MarkupLanguageAttributes name, final String value, final boolean escape)
349       throws IOException {
350     if (!startStillOpen) {
351       final String trace = getCallingClassStackTraceElementString();
352       final String error = "Cannot write attribute when start-tag not open. "
353           + "name = '" + name + "' "
354           + "value = '" + value + "' "
355           + trace.substring(trace.indexOf('('));
356       LOG.error(error);
357       throw new IllegalStateException(error);
358     }
359 
360     if (value != null) {
361       sink.write(' ');
362       sink.write(name.getValue());
363       sink.write("='");
364       writerAttributeValue(value, escape);
365       sink.write('\'');
366     }
367   }
368 
369   protected abstract void writerAttributeValue(String value, boolean escape) throws IOException;
370 
371 
372 }
373