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.lang.invoke.MethodHandles;
33  import java.net.URI;
34  import java.nio.charset.Charset;
35  import java.nio.charset.StandardCharsets;
36  
37  public abstract class TobagoResponseWriterBase extends TobagoResponseWriter {
38  
39    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
40  
41    protected static final String XML_VERSION_1_0_ENCODING_UTF_8 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
42  
43    protected static final char[] XML_VERSION_1_0_ENCODING_UTF_8_CHARS = XML_VERSION_1_0_ENCODING_UTF_8.toCharArray();
44  
45    private int level = 0;
46  
47    private int inlineStack = 0;
48  
49    private UIComponent component;
50  
51    private boolean startStillOpen;
52  
53    private final Writer writer;
54  
55    private final String contentType;
56  
57    private final Charset charset;
58  
59    /**
60     * @deprecated since 4.3.0
61     */
62    @Deprecated
63    protected TobagoResponseWriterBase(final Writer writer, final String contentType, final String characterEncoding) {
64      this(writer, contentType, characterEncoding != null ? Charset.forName(characterEncoding) : StandardCharsets.UTF_8);
65    }
66  
67    protected TobagoResponseWriterBase(final Writer writer, final String contentType, final Charset charset) {
68      this.writer = writer;
69      this.contentType = contentType;
70      this.charset = charset != null ? charset : StandardCharsets.UTF_8;
71    }
72  
73    protected final Writer getWriter() {
74      return writer;
75    }
76  
77    protected final UIComponent getComponent() {
78      return component;
79    }
80  
81    protected final void setComponent(final UIComponent component) {
82      this.component = component;
83    }
84  
85    protected final boolean isStartStillOpen() {
86      return startStillOpen;
87    }
88  
89    protected final void setStartStillOpen(final boolean startStillOpen) {
90      this.startStillOpen = startStillOpen;
91    }
92  
93    protected final String findValue(final Object value, final String property) {
94      if (value != null) {
95        return value instanceof String ? (String) value : value.toString();
96      } else if (property != null) {
97        if (component != null) {
98          final Object object = component.getAttributes().get(property);
99          if (object != null) {
100           return object instanceof String ? (String) object : object.toString();
101         } else {
102           return null;
103         }
104       } else {
105         final String trace = getCallingClassStackTraceElementString();
106         LOG.warn("Don't know what to do! "
107             + "Property defined, but no component to get a value. (value=null, property='" + property + "') "
108             + trace.substring(trace.indexOf('(')));
109         return null;
110       }
111     } else {
112       final String trace = getCallingClassStackTraceElementString();
113       LOG.warn("Don't know what to do! "
114           + "No value and no property defined. (value=null, property=null)"
115           + trace.substring(trace.indexOf('(')));
116       return null;
117     }
118   }
119 
120   @Override
121   public void write(final char[] cbuf, final int off, final int len)
122       throws IOException {
123     writer.write(cbuf, off, len);
124   }
125 
126   @Override
127   public void write(final String string) throws IOException {
128     writeInternal(writer, string);
129   }
130 
131   protected final void writeInternal(final Writer sink, final String string) throws IOException {
132     closeOpenTag();
133     sink.write(string);
134   }
135 
136   @Override
137   public void write(final int j) throws IOException {
138     closeOpenTag();
139     writer.write(j);
140   }
141 
142   @Override
143   public void write(final char[] chars) throws IOException {
144     closeOpenTag();
145     writer.write(chars);
146   }
147 
148   @Override
149   public void write(final String string, final int j, final int k) throws IOException {
150     closeOpenTag();
151     writer.write(string, j, k);
152   }
153 
154   @Override
155   public void close() throws IOException {
156     closeOpenTag();
157     writer.close();
158   }
159 
160   @Override
161   public void flush() throws IOException {
162     /*
163     From the api:
164     Flush any ouput buffered by the output method to the underlying Writer or OutputStream.
165     This method will not flush the underlying Writer or OutputStream;
166     it simply clears any values buffered by this ResponseWriter.
167      */
168     closeOpenTag();
169   }
170 
171   protected void closeOpenTag() throws IOException {
172     if (startStillOpen) {
173       writer.write('>');
174       startStillOpen = false;
175     }
176   }
177 
178   @Override
179   public void startDocument() throws IOException {
180     // nothing to do
181   }
182 
183   @Override
184   public void endDocument() throws IOException {
185     // nothing to do
186   }
187 
188   @Override
189   public String getContentType() {
190     return contentType;
191   }
192 
193   @Override
194   public String getCharacterEncoding() {
195     return charset.name();
196   }
197 
198   @Override
199   public void startElement(final String name, final UIComponent currentComponent) throws IOException {
200     final boolean inline = HtmlElements.isInline(name);
201     if (inline) {
202       inlineStack++;
203     }
204     this.component = currentComponent;
205     startElementInternal(writer, name, HtmlElements.isInline(name));
206   }
207 
208   @Override
209   public void startElement(final HtmlElements name) throws IOException {
210     final boolean inline = name.isInline();
211     if (inline) {
212       inlineStack++;
213     }
214     startElementInternal(writer, name.getValue(), name.isInline());
215     if (!name.isVoid()) {
216       level++;
217     }
218   }
219 
220   protected void startElementInternal(final Writer sink, final String name, final boolean inline)
221       throws IOException {
222     if (startStillOpen) {
223       sink.write('>');
224     }
225     if (inlineStack <= 1) {
226       sink.write('\n');
227       for (int i = 0; i < level; i++) {
228         sink.write(' ');
229       }
230     }
231     sink.write('<');
232     sink.write(name);
233     startStillOpen = true;
234   }
235 
236   @Override
237   public void endElement(final String name) throws IOException {
238     final boolean inline = HtmlElements.isInline(name);
239     if (HtmlElements.isVoid(name)) {
240       closeEmptyTag();
241     } else {
242       endElementInternal(writer, name, inline);
243     }
244     startStillOpen = false;
245     if (inline) {
246       inlineStack--;
247       assert inlineStack >= 0;
248     }
249   }
250 
251   @Override
252   public void endElement(final HtmlElements name) throws IOException {
253     final boolean inline = name.isInline();
254     if (name.isVoid()) {
255       closeEmptyTag();
256     } else {
257       if (!name.isVoid()) {
258         level--;
259       }
260       endElementInternal(writer, name.getValue(), inline);
261     }
262     startStillOpen = false;
263     if (inline) {
264       inlineStack--;
265       assert inlineStack >= 0;
266     }
267   }
268 
269   @Override
270   public void writeComment(final Object obj) throws IOException {
271     closeOpenTag();
272     final String comment = obj.toString();
273     writer.write('\n');
274     for (int i = 0; i < level; i++) {
275       writer.write(' ');
276     }
277     write("<!--");
278     write(comment);
279     write("-->");
280   }
281 
282   /**
283    * @deprecated since 3.0.0
284    */
285   @Override
286   @Deprecated
287   public void writeAttribute(final String name, final Object value, final String property)
288       throws IOException {
289 
290     final String attribute = findValue(value, property);
291     writeAttribute(new MarkupLanguageAttributes() {
292       @Override
293       public String getValue() {
294         return name;
295       }
296     }, attribute, true);
297   }
298 
299   protected final String getCallingClassStackTraceElementString() {
300     final StackTraceElement[] stackTrace = new Exception().getStackTrace();
301     int j = 1;
302     while (stackTrace[j].getClassName().contains("ResponseWriter")) {
303       j++;
304     }
305     return stackTrace[j].toString();
306   }
307 
308   @Override
309   public void writeURIAttribute(final String name, final Object value, final String property)
310       throws IOException {
311     if (value != null) {
312       final URI uri = URI.create(value.toString());
313       writeAttribute(name, uri.toASCIIString(), property);
314     }
315   }
316 
317 // interface TobagoResponseWriter //////////////////////////////////////////////////////////////////////////////////
318 
319   @Override
320   public void writeAttribute(final MarkupLanguageAttributes name, final String value, final boolean escape)
321       throws IOException {
322     writeAttributeInternal(writer, name, value, escape);
323   }
324 
325   @Override
326   public void writeAttribute(final MarkupLanguageAttributes name, final HtmlTypes types) throws IOException {
327     writeAttributeInternal(writer, name, types.getValue(), false);
328   }
329 
330   @Override
331   public void writeURIAttribute(final MarkupLanguageAttributes name, final String value)
332       throws IOException {
333     if (value != null) {
334       final URI uri = URI.create(value);
335       writeAttribute(name, uri.toASCIIString(), true);
336     }
337   }
338 
339   protected void endElementInternal(final Writer sink, final String name, final boolean inline) throws IOException {
340     if (startStillOpen) {
341       sink.write('>');
342     }
343     if (inline) {
344       sink.write("</");
345     } else {
346       sink.write('\n');
347       for (int i = 0; i < level; i++) {
348         sink.write(' ');
349       }
350       sink.write("</");
351     }
352     sink.write(name);
353     sink.write('>');
354   }
355 
356   protected abstract void closeEmptyTag() throws IOException;
357 
358   protected void writeAttributeInternal(
359       final Writer sink, final MarkupLanguageAttributes name, final String value, final boolean escape)
360       throws IOException {
361     if (!startStillOpen) {
362       final String trace = getCallingClassStackTraceElementString();
363       final String error = "Cannot write attribute when start-tag not open. "
364           + "name = '" + name + "' "
365           + "value = '" + value + "' "
366           + trace.substring(trace.indexOf('('));
367       LOG.error(error);
368       throw new IllegalStateException(error);
369     }
370 
371     if (value != null) {
372       sink.write(' ');
373       sink.write(name.getValue());
374       sink.write("='");
375       writerAttributeValue(value, escape);
376       sink.write('\'');
377     }
378   }
379 
380   protected abstract void writerAttributeValue(String value, boolean escape) throws IOException;
381 
382 
383 }
384