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 }