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 import java.util.EmptyStackException;
039
040 public class TobagoResponseWriterImpl extends TobagoResponseWriter {
041
042 private static final Log LOG = LogFactory.getLog(TobagoResponseWriterImpl.class);
043
044 private static final Set<String> EMPTY_TAG = new HashSet<String>(Arrays.asList(
045 HtmlConstants.BR,
046 HtmlConstants.AREA,
047 HtmlConstants.LINK,
048 HtmlConstants.IMG,
049 HtmlConstants.PARAM,
050 HtmlConstants.HR,
051 HtmlConstants.INPUT,
052 HtmlConstants.COL,
053 HtmlConstants.BASE,
054 HtmlConstants.META));
055
056 private Writer writer;
057
058 private UIComponent component;
059
060 private boolean startStillOpen;
061
062 private String contentType;
063
064 private String characterEncoding;
065
066 private Stack<String> stack;
067
068 /**
069 * use XML instead HMTL
070 */
071 private boolean xml;
072
073 private HtmlWriterUtil helper;
074
075 public TobagoResponseWriterImpl(final Writer writer, final String contentType,
076 final String characterEncoding) {
077 // LOG.info("new TobagoResponseWriterImpl!");
078 // final StackTraceElement[] stackTrace = new Exception().getStackTrace();
079 // for (int i = 1; i < stackTrace.length && i < 5; i++) {
080 // LOG.info(" " + stackTrace[i].toString());
081 // }
082 this.writer = writer;
083 this.component = null;
084 this.stack = new Stack<String>();
085 this.contentType = contentType;
086 this.characterEncoding
087 = characterEncoding != null ? characterEncoding : "UTF-8";
088 if ("application/xhtml".equals(contentType)
089 || "application/xml".equals(contentType)
090 || "text/xml".equals(contentType)) {
091 xml = true;
092 }
093 helper = new HtmlWriterUtil(writer, characterEncoding);
094 }
095
096 private String findValue(final Object value, final String property) {
097 if (value != null) {
098 return value instanceof String ? (String) value : value.toString();
099 } else if (property != null) {
100 if (component != null) {
101 final Object object = component.getAttributes().get(property);
102 if (object != null) {
103 return object instanceof String ? (String) object : object.toString();
104 } else {
105 return null;
106 }
107 } else {
108 final String trace = getCallingClassStackTraceElementString();
109 LOG.error("Don't know what to do! "
110 + "Property defined, but no component to get a value. "
111 + trace.substring(trace.indexOf('(')));
112 LOG.error("value = 'null'");
113 LOG.error("property = '" + property + "'");
114 return null;
115 }
116 } else {
117 final String trace = getCallingClassStackTraceElementString();
118 LOG.error("Don't know what to do! "
119 + "No value and no property defined. "
120 + trace.substring(trace.indexOf('(')));
121 LOG.error("value = 'null'");
122 LOG.error("property = 'null'");
123 return null;
124 }
125 }
126
127 public void write(final char[] cbuf, final int off, final int len)
128 throws IOException {
129 writer.write(cbuf, off, len);
130 }
131
132 @Override
133 public void write(String string) throws IOException {
134 closeOpenTag();
135 writer.write(string);
136 }
137
138 @Override
139 public void write(int i) throws IOException {
140 closeOpenTag();
141 writer.write(i);
142 }
143
144 @Override
145 public void write(char[] chars) throws IOException {
146 closeOpenTag();
147 writer.write(chars);
148 }
149
150 @Override
151 public void write(String string, int i, int i1) throws IOException {
152 closeOpenTag();
153 writer.write(string, i, i1);
154 }
155
156 public void close() throws IOException {
157 closeOpenTag();
158 writer.close();
159 }
160
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 public void writeText(final Object text, final String property)
172 throws IOException {
173 closeOpenTag();
174 final String value = findValue(text, property);
175 if (xml) {
176 write(XmlUtils.escape(value));
177 } else {
178 helper.writeText(value);
179 }
180 }
181
182 private void closeOpenTag() throws IOException {
183 if (startStillOpen) {
184 writer.write("\n>");
185 startStillOpen = false;
186 }
187 }
188
189 public void writeText(final char[] text, final int offset, final int length)
190 throws IOException {
191 closeOpenTag();
192 if (xml) {
193 writer.write(XmlUtils.escape(text, offset, length, true));
194 } else {
195 helper.writeText(text, offset, length);
196 }
197 }
198
199 public void startDocument() throws IOException {
200 // nothing to do
201 }
202
203 public void endDocument() throws IOException {
204 // nothing to do
205 }
206
207 public String getContentType() {
208 return contentType;
209 }
210
211 public String getCharacterEncoding() {
212 return characterEncoding;
213 }
214
215 public void startElement(final String name, final UIComponent currentComponent)
216 throws IOException {
217 this.component = currentComponent;
218 stack.push(name);
219 // closeOpenTag();
220 if (startStillOpen) {
221 writer.write("\n>");
222 }
223 writer.write("<");
224 writer.write(name);
225 startStillOpen = true;
226 }
227
228 public void endElement(final String name) throws IOException {
229 if (LOG.isDebugEnabled()) {
230 LOG.debug("end Element: " + name);
231 }
232
233 String top = "";
234 try {
235 top = stack.pop();
236 } catch (EmptyStackException e) {
237 LOG.error("Failed to close element \"" + name + "\"!");
238 throw e;
239 }
240 if (!top.equals(name)) {
241 final String trace = getCallingClassStackTraceElementString();
242 LOG.error("Element end with name='" + name + "' doesn't "
243 + "match with top element on the stack='" + top + "' "
244 + trace.substring(trace.indexOf('(')));
245 }
246
247 if (EMPTY_TAG.contains(name)) {
248 if (xml) {
249 writer.write("\n/>");
250 } else {
251 writer.write("\n>");
252 }
253 } else {
254 if (startStillOpen) {
255 writer.write("\n>");
256 }
257 writer.write("</");
258 writer.write(name);
259 // writer.write("\n>"); // FIXME: this makes problems with Tidy
260 writer.write(">");
261 }
262 startStillOpen = false;
263 }
264
265 public void writeComment(final Object obj) throws IOException {
266 closeOpenTag();
267 String comment = obj.toString();
268 write("<!--");
269 if (comment.indexOf("--") < 0) {
270 write(comment);
271 } else {
272 String trace = getCallingClassStackTraceElementString();
273 LOG.warn(
274 "Comment must not contain the sequence '--', comment = '"
275 + comment + "' " + trace.substring(trace.indexOf('(')));
276 write(StringUtils.replace(comment, "--", "++"));
277 }
278 write("-->");
279 }
280
281 public ResponseWriter cloneWithWriter(final Writer originalWriter) {
282 return new TobagoResponseWriterImpl(
283 originalWriter, getContentType(), getCharacterEncoding());
284 }
285
286 public void writeAttribute(final String name, final Object value, final String property)
287 throws IOException {
288
289 final String attribute = findValue(value, property);
290 writeAttribute(name, attribute, true);
291 }
292
293 private String getCallingClassStackTraceElementString() {
294 final StackTraceElement[] stackTrace = new Exception().getStackTrace();
295 int i = 1;
296 while (stackTrace[i].getClassName().equals(this.getClass().getName())) {
297 i++;
298 }
299 return stackTrace[i].toString();
300 }
301
302 public void writeURIAttribute(final String s, final Object obj, final String s1)
303 throws IOException {
304 LOG.error("Not implemented yet!");
305 }
306
307 // interface TobagoResponseWriter //////////////////////////////////////////////////////////////////////////////////
308
309 public void writeAttribute(final String name, final String value, final boolean escape)
310 throws IOException {
311 if (!startStillOpen) {
312 String trace = getCallingClassStackTraceElementString();
313 String error = "Cannot write attribute when start-tag not open. "
314 + "name = '" + name + "' "
315 + "value = '" + value + "' "
316 + trace.substring(trace.indexOf('('));
317 LOG.error(error);
318 throw new IllegalStateException(error);
319 }
320
321 if (value != null) {
322 writer.write(' ');
323 writer.write(name);
324 writer.write("=\"");
325 if (xml) {
326 writer.write(XmlUtils.escape(value));
327 } else {
328 if (escape) {
329 helper.writeAttributeValue(value);
330 } else {
331 writer.write(value);
332 }
333 }
334 writer.write('\"');
335 }
336 }
337
338 public void writeClassAttribute() throws IOException {
339 Object clazz = component.getAttributes().get(ATTR_STYLE_CLASS);
340 if (clazz != null) {
341 writeAttribute(HtmlAttributes.CLASS, clazz.toString(), false);
342 }
343 }
344
345 public void writeStyleAttribute() throws IOException {
346 Object style = component.getAttributes().get(ATTR_STYLE);
347 if (style != null) {
348 writeAttribute(HtmlAttributes.STYLE, style.toString(), false);
349 }
350 }
351 }