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 }