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.stack = new Stack<String>(); 084 this.contentType = contentType; 085 this.characterEncoding 086 = characterEncoding != null ? characterEncoding : "UTF-8"; 087 if ("application/xhtml".equals(contentType) 088 || "application/xml".equals(contentType) 089 || "text/xml".equals(contentType)) { 090 xml = true; 091 } 092 helper = new HtmlWriterUtil(writer, characterEncoding); 093 } 094 095 private String findValue(final Object value, final String property) { 096 if (value != null) { 097 return value instanceof String ? (String) value : value.toString(); 098 } else if (property != null) { 099 if (component != null) { 100 final Object object = component.getAttributes().get(property); 101 if (object != null) { 102 return object instanceof String ? (String) object : object.toString(); 103 } else { 104 return null; 105 } 106 } else { 107 final String trace = getCallingClassStackTraceElementString(); 108 LOG.error("Don't know what to do! " 109 + "Property defined, but no component to get a value. " 110 + trace.substring(trace.indexOf('('))); 111 LOG.error("value = 'null'"); 112 LOG.error("property = '" + property + "'"); 113 return null; 114 } 115 } else { 116 final String trace = getCallingClassStackTraceElementString(); 117 LOG.error("Don't know what to do! " 118 + "No value and no property defined. " 119 + trace.substring(trace.indexOf('('))); 120 LOG.error("value = 'null'"); 121 LOG.error("property = 'null'"); 122 return null; 123 } 124 } 125 126 public void write(final char[] cbuf, final int off, final int len) 127 throws IOException { 128 writer.write(cbuf, off, len); 129 } 130 131 @Override 132 public void write(String string) throws IOException { 133 closeOpenTag(); 134 writer.write(string); 135 } 136 137 @Override 138 public void write(int i) throws IOException { 139 closeOpenTag(); 140 writer.write(i); 141 } 142 143 @Override 144 public void write(char[] chars) throws IOException { 145 closeOpenTag(); 146 writer.write(chars); 147 } 148 149 @Override 150 public void write(String string, int i, int i1) throws IOException { 151 closeOpenTag(); 152 writer.write(string, i, i1); 153 } 154 155 public void close() throws IOException { 156 closeOpenTag(); 157 writer.close(); 158 } 159 160 public void flush() throws IOException { 161 /* 162 From the api: 163 Flush any ouput buffered by the output method to the underlying Writer or OutputStream. 164 This method will not flush the underlying Writer or OutputStream; 165 it simply clears any values buffered by this ResponseWriter. 166 */ 167 closeOpenTag(); 168 } 169 170 public void writeText(final Object text, final String property) 171 throws IOException { 172 closeOpenTag(); 173 final String value = findValue(text, property); 174 if (xml) { 175 write(XmlUtils.escape(value)); 176 } else { 177 helper.writeText(value); 178 } 179 } 180 181 private void closeOpenTag() throws IOException { 182 if (startStillOpen) { 183 writer.write("\n>"); 184 startStillOpen = false; 185 } 186 } 187 188 public void writeText(final char[] text, final int offset, final int length) 189 throws IOException { 190 closeOpenTag(); 191 if (xml) { 192 writer.write(XmlUtils.escape(String.valueOf(text)).toCharArray(), offset, length); 193 // FIXME: not nice: XmlUtils.escape(text.toString()).toCharArray() 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 }