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