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.FastStringWriter;
23  import org.apache.myfaces.tobago.internal.util.JavascriptWriterUtils;
24  import org.apache.myfaces.tobago.internal.util.StringUtils;
25  import org.apache.myfaces.tobago.renderkit.html.MarkupLanguageAttributes;
26  import org.apache.myfaces.tobago.util.FacesVersion;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import javax.faces.component.UIComponent;
31  import javax.faces.context.ResponseWriter;
32  import java.io.IOException;
33  import java.io.Writer;
34  
35  public class JsonResponseWriter extends HtmlResponseWriter {
36  
37    private static final Logger LOG = LoggerFactory.getLogger(JsonResponseWriter.class);
38  
39    private Writer javascriptWriter;
40    private boolean javascriptBlock;
41    private JavascriptWriterUtils encodeInJavascriptBlock;
42    private JavascriptWriterUtils encodeOutsideJavascriptBlock;
43  
44    public JsonResponseWriter(final Writer writer, final String contentType, final String characterEncoding) {
45      super(writer, contentType, characterEncoding);
46      this.javascriptWriter = new FastStringWriter();
47      this.encodeOutsideJavascriptBlock = new JavascriptWriterUtils(writer, characterEncoding);
48      this.encodeInJavascriptBlock = new JavascriptWriterUtils(javascriptWriter, characterEncoding);
49    }
50  
51    @Override
52    public void endJavascript() throws IOException {
53      javascriptBlock = false;
54    }
55  
56    @Override
57    public void startJavascript() throws IOException {
58      javascriptBlock = true;
59    }
60  
61    @Override
62    public void write(final String string) throws IOException {
63      closeOpenTag();
64      if (FacesVersion.isMojarra() && FacesVersion.supports21() && XML_VERSION_1_0_ENCODING_UTF_8.equals(string)) {
65        // ignore
66        return;
67      }
68      if (javascriptBlock) {
69        encodeInJavascriptBlock.writeText(string);
70      } else {
71        encodeOutsideJavascriptBlock.writeText(string);
72      }
73    }
74  
75    @Override
76    public void write(final char[] chars) throws IOException {
77      // XXX remove me later:
78      // this is a temporary workaround, should be removed after fixing the bug in Mojarra.
79      // http://java.net/jira/browse/JAVASERVERFACES-2411
80      // https://issues.apache.org/jira/browse/TOBAGO-1124
81      if (FacesVersion.isMojarra() && FacesVersion.supports20()) {
82        final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
83        if (stackTraceElements[2].getClassName().equals("com.sun.faces.renderkit.ServerSideStateHelper")) {
84          super.write(StringUtils.replace(new String(chars), "\"", "\\\""));
85          return;
86        }
87      }
88      super.write(chars);
89    }
90  
91    @Override
92    public void writeJavascript(final String script) throws IOException {
93      closeOpenTag();
94      encodeInJavascriptBlock.writeText(script);
95    }
96  
97    @Override
98    public String getJavascript() {
99      return javascriptWriter.toString();
100   }
101 
102   @Override
103   protected void startElementInternal(final Writer writer, final String name, final UIComponent currentComponent)
104       throws IOException {
105     setComponent(currentComponent);
106     if (isStartStillOpen()) {
107       writer.write(">");
108     }
109     writer.write("<");
110     writer.write(name);
111     setStartStillOpen(true);
112   }
113 
114   @Override
115   protected void endElementInternal(final Writer writer, final String name, final boolean inline) throws IOException {
116     if (isStartStillOpen()) {
117       writer.write(">");
118     }
119     writer.write("</");
120     writer.write(name);
121     writer.write(">");
122     setStartStillOpen(false);
123   }
124 
125   @Override
126   protected void closeOpenTag() throws IOException {
127     if (isStartStillOpen()) {
128       getWriter().write(">");
129       setStartStillOpen(false);
130     }
131   }
132 
133   @Override
134   protected final void closeEmptyTag() throws IOException {
135     getWriter().write(">");
136   }
137 
138   @Override
139   protected void writeAttributeInternal(
140       final Writer writer, final MarkupLanguageAttributes name, final String value, final boolean escape)
141       throws IOException {
142     if (!isStartStillOpen()) {
143       final String trace = getCallingClassStackTraceElementString();
144       final String error = "Cannot write attribute when start-tag not open. "
145           + "name = '" + name + "' "
146           + "value = '" + value + "' "
147           + trace.substring(trace.indexOf('('));
148       LOG.error(error);
149       throw new IllegalStateException(error);
150     }
151 
152     if (value != null) {
153       writer.write(' ');
154       writer.write(name.getValue());
155       writer.write("='");
156 
157       if (escape) {
158         getHelper().writeAttributeValue(value);
159       } else {
160         writer.write(value);
161       }
162       writer.write("'");
163     }
164   }
165 
166   @Override
167   public void writeText(final Object text, final String property)
168       throws IOException {
169     closeOpenTag();
170     final String value = findValue(text, property);
171     getHelper().writeText(value);
172   }
173 
174 /* TODO: may also encode the backslash \, but will not be used currently
175   public void writeText(final char[] text, final int offset, final int length)
176       throws IOException {
177     closeOpenTag();
178     getHelper().writeText(text, offset, length);
179   }
180 */
181 
182   @Override
183   public ResponseWriter cloneWithWriter(final Writer originalWriter) {
184      return new JsonResponseWriter(
185          originalWriter, getContentType(), getCharacterEncoding());
186    }
187 
188 
189   @Override
190   public void startDocument() throws IOException {
191   }
192 
193   @Override
194   public void endDocument() throws IOException {
195   }
196 }