001    package org.apache.myfaces.tobago.webapp;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE;
024    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_CLASS;
025    import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
026    import org.apache.myfaces.tobago.renderkit.html.HtmlConstants;
027    import org.apache.myfaces.tobago.util.HtmlWriterUtil;
028    import org.apache.myfaces.tobago.util.XmlUtils;
029    
030    import javax.faces.component.UIComponent;
031    import javax.faces.context.ResponseWriter;
032    import java.io.IOException;
033    import java.io.Writer;
034    import java.util.Arrays;
035    import java.util.HashSet;
036    import java.util.Set;
037    import java.util.Stack;
038    
039    public class TobagoResponseWriterImpl extends TobagoResponseWriter {
040    
041      private static final Log LOG = LogFactory.getLog(TobagoResponseWriterImpl.class);
042    
043      private static final Set<String> EMPTY_TAG = new HashSet<String>(Arrays.asList(
044          HtmlConstants.BR,
045          HtmlConstants.AREA,
046          HtmlConstants.LINK,
047          HtmlConstants.IMG,
048          HtmlConstants.PARAM,
049          HtmlConstants.HR,
050          HtmlConstants.INPUT,
051          HtmlConstants.COL,
052          HtmlConstants.BASE,
053          HtmlConstants.META));
054    
055      private Writer writer;
056    
057      private UIComponent component;
058    
059      private boolean startStillOpen;
060    
061      private String contentType;
062    
063      private String characterEncoding;
064    
065      private Stack<String> stack;
066    
067      /**
068       * use XML instead HMTL
069       */
070      private boolean xml;
071    
072      private HtmlWriterUtil helper;
073    
074      public TobagoResponseWriterImpl(final Writer writer, final String contentType,
075                                  final String characterEncoding) {
076    //    LOG.info("new TobagoResponseWriterImpl!");
077    //    final StackTraceElement[] stackTrace = new Exception().getStackTrace();
078    //    for (int i = 1; i < stackTrace.length && i < 5; i++) {
079    //      LOG.info("  " + stackTrace[i].toString());
080    //    }
081        this.writer = writer;
082        this.stack = new Stack<String>();
083        this.contentType = contentType;
084        this.characterEncoding
085            = characterEncoding != null ? characterEncoding : "UTF-8";
086        if ("application/xhtml".equals(contentType)
087            || "application/xml".equals(contentType)
088            || "text/xml".equals(contentType)) {
089          xml = true;
090        }
091        helper = new HtmlWriterUtil(writer, characterEncoding);
092      }
093    
094      private String findValue(final Object value, final String property) {
095        if (value != null) {
096          return value instanceof String ? (String) value : value.toString();
097        } else if (property != null) {
098          if (component != null) {
099            final Object object = component.getAttributes().get(property);
100            if (object != null) {
101              return object instanceof String ? (String) object : object.toString();
102            } else {
103              return null;
104            }
105          } else {
106            final String trace = getCallingClassStackTraceElementString();
107            LOG.error("Don't know what to do! "
108                + "Property defined, but no component to get a value. "
109                + trace.substring(trace.indexOf('(')));
110            LOG.error("value = 'null'");
111            LOG.error("property = '" + property + "'");
112            return null;
113          }
114        } else {
115          final String trace = getCallingClassStackTraceElementString();
116          LOG.error("Don't know what to do! "
117              + "No value and no property defined. "
118              + trace.substring(trace.indexOf('(')));
119          LOG.error("value = 'null'");
120          LOG.error("property = 'null'");
121          return null;
122        }
123      }
124    
125      public void write(final char[] cbuf, final int off, final int len)
126          throws IOException {
127        writer.write(cbuf, off, len);
128      }
129    
130      @Override
131      public void write(String string) throws IOException {
132        closeOpenTag();
133        writer.write(string);
134      }
135    
136      @Override
137      public void write(int i) throws IOException {
138        closeOpenTag();
139        writer.write(i);
140      }
141    
142      @Override
143      public void write(char[] chars) throws IOException {
144        closeOpenTag();
145        writer.write(chars);
146      }
147    
148      @Override
149      public void write(String string, int i, int i1) throws IOException {
150        closeOpenTag();
151        writer.write(string, i, i1);
152      }
153    
154      public void close() throws IOException {
155        closeOpenTag();
156        writer.close();
157      }
158    
159      public void flush() throws IOException {
160        /*
161        From the api:
162        Flush any ouput buffered by the output method to the underlying Writer or OutputStream.
163        This method will not flush the underlying Writer or OutputStream;
164        it simply clears any values buffered by this ResponseWriter.
165         */
166        closeOpenTag();
167      }
168    
169      public void writeText(final Object text, final String property)
170          throws IOException {
171        closeOpenTag();
172        final String value = findValue(text, property);
173        if (xml) {
174          write(XmlUtils.escape(value));
175        } else {
176          helper.writeText(value);
177        }
178      }
179    
180      private void closeOpenTag() throws IOException {
181        if (startStillOpen) {
182          writer.write("\n>");
183          startStillOpen = false;
184        }
185      }
186    
187      public void writeText(final char[] text, final int offset, final int length)
188          throws IOException {
189        closeOpenTag();
190        if (xml) {
191          writer.write(XmlUtils.escape(text.toString()).toCharArray(), offset, length);
192    // FIXME: not nice:     XmlUtils.escape(text.toString()).toCharArray()
193        } else {
194          helper.writeText(text, offset, length);
195        }
196      }
197    
198      public void startDocument() throws IOException {
199        // nothing to do
200      }
201    
202      public void endDocument() throws IOException {
203        // nothing to do
204      }
205    
206      public String getContentType() {
207        return contentType;
208      }
209    
210      public String getCharacterEncoding() {
211        return characterEncoding;
212      }
213    
214      public void startElement(final String name, final UIComponent currentComponent)
215          throws IOException {
216        this.component = currentComponent;
217        stack.push(name);
218    //    closeOpenTag();
219        if (startStillOpen) {
220          writer.write("\n>");
221        }
222        writer.write("<");
223        writer.write(name);
224        startStillOpen = true;
225      }
226    
227      public void endElement(final String name) throws IOException {
228        if (LOG.isDebugEnabled()) {
229          LOG.debug("end Element: " + name);
230        }
231    
232        final String top = stack.pop();
233        if (!top.equals(name)) {
234          final String trace = getCallingClassStackTraceElementString();
235          LOG.error("Element end with name='" + name + "' doesn't "
236              + "match with top element on the stack='" + top + "' "
237              + trace.substring(trace.indexOf('(')));
238        }
239    
240        if (EMPTY_TAG.contains(name)) {
241          if (xml) {
242            writer.write("\n/>");
243          } else {
244            writer.write("\n>");
245          }
246        } else {
247          if (startStillOpen) {
248            writer.write("\n>");
249          }
250          writer.write("</");
251          writer.write(name);
252    //      writer.write("\n>"); // FIXME: this makes problems with Tidy
253          writer.write(">");
254        }
255        startStillOpen = false;
256      }
257    
258      public void writeComment(final Object obj) throws IOException {
259        closeOpenTag();
260        String comment = obj.toString();
261        write("<!--");
262        if (comment.indexOf("--") < 0) {
263          write(comment);
264        } else {
265          String trace = getCallingClassStackTraceElementString();
266          LOG.warn(
267              "Comment must not contain the sequence '--', comment = '"
268                  + comment + "' " + trace.substring(trace.indexOf('(')));
269          write(StringUtils.replace(comment, "--", "++"));
270        }
271        write("-->");
272      }
273    
274      public ResponseWriter cloneWithWriter(final Writer originalWriter) {
275        return new TobagoResponseWriterImpl(
276            originalWriter, getContentType(), getCharacterEncoding());
277      }
278    
279      public void writeAttribute(final String name, final Object value, final String property)
280          throws IOException {
281    
282        final String attribute = findValue(value, property);
283        writeAttribute(name, attribute, true);
284      }
285    
286      private String getCallingClassStackTraceElementString() {
287        final StackTraceElement[] stackTrace = new Exception().getStackTrace();
288        int i = 1;
289        while (stackTrace[i].getClassName().equals(this.getClass().getName())) {
290          i++;
291        }
292        return stackTrace[i].toString();
293      }
294    
295      public void writeURIAttribute(final String s, final Object obj, final String s1)
296          throws IOException {
297        LOG.error("Not implemented yet!");
298      }
299    
300    // interface TobagoResponseWriter //////////////////////////////////////////////////////////////////////////////////
301    
302      public void writeAttribute(final String name, final String value, final boolean escape)
303          throws IOException {
304        if (!startStillOpen) {
305          String trace = getCallingClassStackTraceElementString();
306          String error = "Cannot write attribute when start-tag not open. "
307              + "name = '" + name + "' "
308              + "value = '" + value + "' "
309              + trace.substring(trace.indexOf('('));
310          LOG.error(error);
311          throw new IllegalStateException(error);
312        }
313    
314        if (value != null) {
315          writer.write(' ');
316          writer.write(name);
317          writer.write("=\"");
318          if (xml) {
319            writer.write(XmlUtils.escape(value));
320          } else {
321            if (escape) {
322              helper.writeAttributeValue(value);
323            } else {
324              writer.write(value);
325            }
326          }
327          writer.write('\"');
328        }
329      }
330    
331      public void writeClassAttribute() throws IOException {
332        Object clazz = component.getAttributes().get(ATTR_STYLE_CLASS);
333        if (clazz != null) {
334          writeAttribute(HtmlAttributes.CLASS, clazz.toString(), false);
335        }
336      }
337    
338      public void writeStyleAttribute() throws IOException {
339        Object style = component.getAttributes().get(ATTR_STYLE);
340        if (style != null) {
341          writeAttribute(HtmlAttributes.STYLE, style.toString(), false);
342        }
343      }
344    }