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