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