Coverage Report - org.apache.myfaces.shared_impl.renderkit.html.HtmlResponseWriterImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
HtmlResponseWriterImpl
0%
0/217
0%
0/136
3.241
 
 1  
 /*
 2  
  *  Licensed to the Apache Software Foundation (ASF) under one
 3  
  *  or more contributor license agreements.  See the NOTICE file
 4  
  *  distributed with this work for additional information
 5  
  *  regarding copyright ownership.  The ASF licenses this file
 6  
  *  to you under the Apache License, Version 2.0 (the
 7  
  *  "License"); you may not use this file except in compliance
 8  
  *  with the License.  You may obtain a copy of the License at
 9  
  * 
 10  
  *  http://www.apache.org/licenses/LICENSE-2.0
 11  
  * 
 12  
  *  Unless required by applicable law or agreed to in writing,
 13  
  *  software distributed under the License is distributed on an
 14  
  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  *  KIND, either express or implied.  See the License for the
 16  
  *  specific language governing permissions and limitations
 17  
  *  under the License.
 18  
  */
 19  
 package org.apache.myfaces.shared_impl.renderkit.html;
 20  
 
 21  
 import org.apache.commons.logging.Log;
 22  
 import org.apache.commons.logging.LogFactory;
 23  
 import org.apache.myfaces.shared_impl.renderkit.RendererUtils;
 24  
 import org.apache.myfaces.shared_impl.renderkit.html.util.UnicodeEncoder;
 25  
 
 26  
 import javax.faces.FacesException;
 27  
 import javax.faces.component.UIComponent;
 28  
 import javax.faces.context.FacesContext;
 29  
 import javax.faces.context.ResponseWriter;
 30  
 import java.io.IOException;
 31  
 import java.io.UnsupportedEncodingException;
 32  
 import java.io.Writer;
 33  
 import java.util.HashSet;
 34  
 import java.util.Set;
 35  
 
 36  
 /**
 37  
  * @author Manfred Geiler (latest modification by $Author: lu4242 $)
 38  
  * @author Anton Koinov
 39  
  * @version $Revision: 779412 $ $Date: 2009-05-27 21:36:25 -0500 (Wed, 27 May 2009) $
 40  
  */
 41  
 public class HtmlResponseWriterImpl
 42  
         extends ResponseWriter
 43  
 {
 44  0
     private static final Log log = LogFactory.getLog(HtmlResponseWriterImpl.class);
 45  
 
 46  
     private static final String DEFAULT_CONTENT_TYPE = "text/html";
 47  
     private static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
 48  
     private static final String UTF8 = "UTF-8";
 49  
 
 50  0
     private boolean _writeDummyForm = false;
 51  0
     private Set _dummyFormParams = null;
 52  
 
 53  
     private Writer _writer;
 54  
     private String _contentType;
 55  
     private String _characterEncoding;
 56  
     private String _startElementName;
 57  
     private Boolean _isScript;
 58  
     private Boolean _isStyle;
 59  
     private Boolean _isTextArea;
 60  
     private UIComponent _startElementUIComponent;
 61  
     private boolean _startTagOpen;
 62  
 
 63  0
     private static final Set s_emptyHtmlElements = new HashSet();
 64  
 
 65  
     private static final String CDATA_START = "<![CDATA[ \n";
 66  
     private static final String COMMENT_START = "<!--\n";
 67  
     private static final String CDATA_COMMENT_END = "\n//]]>";
 68  
     private static final String CDATA_END = "\n]]>";
 69  
     private static final String COMMENT_COMMENT_END = "\n//-->";
 70  
     private static final String COMMENT_END = "\n-->";
 71  
 
 72  
     static
 73  
     {
 74  0
         s_emptyHtmlElements.add("area");
 75  0
         s_emptyHtmlElements.add("br");
 76  0
         s_emptyHtmlElements.add("base");
 77  0
         s_emptyHtmlElements.add("basefont");
 78  0
         s_emptyHtmlElements.add("col");
 79  0
         s_emptyHtmlElements.add("frame");
 80  0
         s_emptyHtmlElements.add("hr");
 81  0
         s_emptyHtmlElements.add("img");
 82  0
         s_emptyHtmlElements.add("input");
 83  0
         s_emptyHtmlElements.add("isindex");
 84  0
         s_emptyHtmlElements.add("link");
 85  0
         s_emptyHtmlElements.add("meta");
 86  0
         s_emptyHtmlElements.add("param");
 87  0
     }
 88  
 
 89  
     public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding)
 90  
     throws FacesException
 91  0
     {
 92  0
         _writer = writer;
 93  0
         _contentType = contentType;
 94  0
         if (_contentType == null)
 95  
         {
 96  0
             if (log.isDebugEnabled()) log.debug("No content type given, using default content type " + DEFAULT_CONTENT_TYPE);
 97  0
             _contentType = DEFAULT_CONTENT_TYPE;
 98  
         }
 99  0
         if (characterEncoding == null)
 100  
         {
 101  0
             if (log.isDebugEnabled()) log.debug("No character encoding given, using default character encoding " + DEFAULT_CHARACTER_ENCODING);
 102  0
             _characterEncoding = DEFAULT_CHARACTER_ENCODING;
 103  
         }
 104  
         else
 105  
         {
 106  
             // validates the encoding, it will throw an UnsupportedEncodingException if the encoding is invalid
 107  
             try
 108  
             {
 109  0
                 new String("myfaces".getBytes(), characterEncoding);
 110  
             }
 111  0
             catch (UnsupportedEncodingException e)
 112  
             {
 113  0
                 throw new IllegalArgumentException("Unsupported encoding: "+characterEncoding);
 114  0
             }
 115  
             
 116  
             // canonize to uppercase, that's the standard format
 117  0
             _characterEncoding = characterEncoding.toUpperCase();
 118  
         }
 119  0
     }
 120  
 
 121  
     public static boolean supportsContentType(String contentType)
 122  
     {
 123  0
         String[] supportedContentTypes = HtmlRendererUtils.getSupportedContentTypes();
 124  
 
 125  0
         for (int i = 0; i < supportedContentTypes.length; i++)
 126  
         {
 127  0
             String supportedContentType = supportedContentTypes[i];
 128  
 
 129  0
             if(supportedContentType.indexOf(contentType)!=-1)
 130  0
                 return true;
 131  
         }
 132  0
         return false;
 133  
     }
 134  
 
 135  
     public String getContentType()
 136  
     {
 137  0
         return _contentType;
 138  
     }
 139  
 
 140  
     public String getCharacterEncoding()
 141  
     {
 142  0
         return _characterEncoding;
 143  
     }
 144  
 
 145  
     public void flush() throws IOException
 146  
     {
 147  
         // API doc says we should not flush the underlying writer
 148  
         //_writer.flush();
 149  
         // but rather clear any values buffered by this ResponseWriter:
 150  0
         closeStartTagIfNecessary();
 151  0
     }
 152  
 
 153  
     public void startDocument()
 154  
     {
 155  
         // do nothing
 156  0
     }
 157  
 
 158  
     public void endDocument() throws IOException
 159  
     {
 160  0
         _writer.flush();
 161  0
     }
 162  
 
 163  
     public void startElement(String name, UIComponent uiComponent) throws IOException
 164  
     {
 165  0
         if (name == null)
 166  
         {
 167  0
             throw new NullPointerException("elementName name must not be null");
 168  
         }
 169  
 
 170  0
         closeStartTagIfNecessary();
 171  0
         _writer.write('<');
 172  0
         _writer.write(name);
 173  
 
 174  0
         resetStartedElement();
 175  
 
 176  0
         _startElementName = name;
 177  0
         _startElementUIComponent = uiComponent;
 178  0
         _startTagOpen = true;
 179  0
     }
 180  
 
 181  
     private void closeStartTagIfNecessary() throws IOException
 182  
     {
 183  0
         if (_startTagOpen)
 184  
         {
 185  0
             if (s_emptyHtmlElements.contains(_startElementName.toLowerCase()))
 186  
             {
 187  0
                 _writer.write(" />");
 188  
                 // make null, this will cause NullPointer in some invalid element nestings
 189  
                 // (better than doing nothing)
 190  0
                 resetStartedElement();
 191  
             }
 192  
             else
 193  
             {
 194  0
                 _writer.write('>');
 195  
 
 196  0
                 if(isScriptOrStyle())
 197  
                 {
 198  0
                     if(HtmlRendererUtils.isXHTMLContentType(_contentType))
 199  
                     {
 200  0
                         if(HtmlRendererUtils.isAllowedCdataSection(FacesContext.getCurrentInstance()))
 201  
                         {
 202  0
                             _writer.write(CDATA_START);
 203  
                         }
 204  
                     }
 205  
                     else
 206  
                     {
 207  0
                         _writer.write(COMMENT_START);
 208  
                     }
 209  
                 }
 210  
             }
 211  0
             _startTagOpen = false;
 212  
         }
 213  0
     }
 214  
 
 215  
     private void resetStartedElement()
 216  
     {
 217  0
         _startElementName = null;
 218  0
         _startElementUIComponent = null;
 219  0
         _isScript = null;
 220  0
         _isStyle = null;
 221  0
         _isTextArea = null;
 222  0
     }
 223  
 
 224  
     public void endElement(String name) throws IOException
 225  
     {
 226  0
         if (name == null)
 227  
         {
 228  0
             throw new NullPointerException("elementName name must not be null");
 229  
         }
 230  
 
 231  0
         if (log.isWarnEnabled())
 232  
         {
 233  0
             if (_startElementName != null &&
 234  
                 !name.equals(_startElementName))
 235  
             {
 236  0
                 if (log.isWarnEnabled())
 237  0
                     log.warn("HTML nesting warning on closing " + name + ": element " + _startElementName +
 238  
                             (_startElementUIComponent==null?"":(" rendered by component : "+
 239  
                             RendererUtils.getPathToComponent(_startElementUIComponent)))+" not explicitly closed");
 240  
             }
 241  
         }
 242  
 
 243  0
         if(_startTagOpen)
 244  
         {
 245  
 
 246  
             // we will get here only if no text or attribute was written after the start element was opened
 247  
             // now we close out the started tag - if it is an empty tag, this is then fully closed
 248  0
             closeStartTagIfNecessary();
 249  
 
 250  
             //tag was no empty tag - it has no accompanying end tag now.
 251  0
             if(_startElementName!=null)
 252  
             {
 253  
                 //write closing tag
 254  0
                 writeEndTag(name);
 255  
             }
 256  
         }
 257  
         else
 258  
         {
 259  0
             if (s_emptyHtmlElements.contains(name.toLowerCase()))
 260  
             {
 261  
            /*
 262  
            Should this be here?  It warns even when you have an x:htmlTag value="br", it should just close.
 263  
 
 264  
                 if (log.isWarnEnabled())
 265  
                     log.warn("HTML nesting warning on closing " + name + ": This element must not contain nested elements or text in HTML");
 266  
                     */
 267  
             }
 268  
             else
 269  
             {
 270  0
                 writeEndTag(name);
 271  
             }
 272  
         }
 273  
 
 274  0
         resetStartedElement();
 275  0
     }
 276  
 
 277  
     private void writeEndTag(String name)
 278  
         throws IOException
 279  
     {
 280  0
         if(isScriptOrStyle())
 281  
         {
 282  0
             if(HtmlRendererUtils.isXHTMLContentType(_contentType))
 283  
             {
 284  0
                 if(HtmlRendererUtils.isAllowedCdataSection(FacesContext.getCurrentInstance()))
 285  
                 {
 286  0
                     if(isScript())
 287  0
                         _writer.write(CDATA_COMMENT_END);
 288  
                     else
 289  0
                         _writer.write(CDATA_END);
 290  
                 }
 291  
             }
 292  
             else
 293  
             {
 294  0
                 if(isScript())
 295  0
                     _writer.write(COMMENT_COMMENT_END);
 296  
                 else
 297  0
                     _writer.write(COMMENT_END);
 298  
             }
 299  
         }
 300  
 
 301  0
         _writer.write("</");
 302  0
         _writer.write(name);
 303  0
         _writer.write('>');
 304  0
     }
 305  
 
 306  
     public void writeAttribute(String name, Object value, String componentPropertyName) throws IOException
 307  
     {
 308  0
         if (name == null)
 309  
         {
 310  0
             throw new NullPointerException("attributeName name must not be null");
 311  
         }
 312  0
         if (!_startTagOpen)
 313  
         {
 314  0
             throw new IllegalStateException("Must be called before the start element is closed (attribute '" + name + "')");
 315  
         }
 316  
 
 317  0
         if (value instanceof Boolean)
 318  
         {
 319  0
             if (((Boolean)value).booleanValue())
 320  
             {
 321  
                 // name as value for XHTML compatibility
 322  0
                 _writer.write(' ');
 323  0
                 _writer.write(name);
 324  0
                 _writer.write("=\"");
 325  0
                 _writer.write(name);
 326  0
                 _writer.write('"');
 327  
             }
 328  
         }
 329  
         else
 330  
         {
 331  0
             String strValue = (value==null)?"":value.toString();
 332  0
             _writer.write(' ');
 333  0
             _writer.write(name);
 334  0
             _writer.write("=\"");
 335  0
             _writer.write(org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder.encode(strValue, false, false, !UTF8.equals(_characterEncoding)));
 336  0
             _writer.write('"');
 337  
         }
 338  0
     }
 339  
 
 340  
     public void writeURIAttribute(String name, Object value, String componentPropertyName) throws IOException
 341  
     {
 342  0
         if (name == null)
 343  
         {
 344  0
             throw new NullPointerException("attributeName name must not be null");
 345  
         }
 346  0
         if (!_startTagOpen)
 347  
         {
 348  0
             throw new IllegalStateException("Must be called before the start element is closed (attribute '" + name + "')");
 349  
         }
 350  
 
 351  0
         String strValue = value.toString();
 352  0
         _writer.write(' ');
 353  0
         _writer.write(name);
 354  0
         _writer.write("=\"");
 355  0
         if (strValue.toLowerCase().startsWith("javascript:"))
 356  
         {
 357  0
             _writer.write(org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder.encode(strValue, false, false, !UTF8.equals(_characterEncoding)));
 358  
         }
 359  
         else
 360  
         {
 361  
             /*
 362  
             Todo: what is this section about? still needed?
 363  
             client side state saving is now done via javascript...
 364  
 
 365  
             if (_startElementName.equalsIgnoreCase(HTML.ANCHOR_ELEM) && //Also support image and button urls?
 366  
                 name.equalsIgnoreCase(HTML.HREF_ATTR) &&
 367  
                 !strValue.startsWith("#"))
 368  
             {
 369  
                 FacesContext facesContext = FacesContext.getCurrentInstance();
 370  
                 if (facesContext.getApplication().getStateManager().isSavingStateInClient(facesContext))
 371  
                 {
 372  
                     // saving state in url depends on the work together
 373  
                     // of 3 (theoretically) pluggable components:
 374  
                     // ViewHandler, ResponseWriter and ViewTag
 375  
                     // We should try to make this HtmlResponseWriterImpl able
 376  
                     // to handle this alone!
 377  
                     if (strValue.indexOf('?') < 0)
 378  
                     {
 379  
                         strValue = strValue + '?' + JspViewHandlerImpl.URL_STATE_MARKER;
 380  
                     }
 381  
                     else
 382  
                     {
 383  
                         strValue = strValue + '&' + JspViewHandlerImpl.URL_STATE_MARKER;
 384  
                     }
 385  
                 }
 386  
             }
 387  
             */
 388  
             //_writer.write(strValue);
 389  0
             _writer.write(org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder.encodeURIAtributte(strValue, _characterEncoding));
 390  
         }
 391  0
         _writer.write('"');
 392  0
     }
 393  
 
 394  
     public void writeComment(Object value) throws IOException
 395  
     {
 396  0
         if (value == null)
 397  
         {
 398  0
             throw new NullPointerException("comment name must not be null");
 399  
         }
 400  
 
 401  0
         closeStartTagIfNecessary();
 402  0
         _writer.write("<!--");
 403  0
         _writer.write(value.toString());    //TODO: Escaping: must not have "-->" inside!
 404  0
         _writer.write("-->");
 405  0
     }
 406  
 
 407  
     public void writeText(Object value, String componentPropertyName) throws IOException
 408  
     {
 409  0
         if (value == null)
 410  
         {
 411  0
             throw new NullPointerException("Text must not be null.");
 412  
         }
 413  
 
 414  0
         closeStartTagIfNecessary();
 415  
 
 416  0
         String strValue = value.toString();
 417  
 
 418  0
         if (isScriptOrStyle())
 419  
         {
 420  
             // Don't bother encoding anything if chosen character encoding is UTF-8
 421  0
             if (UTF8.equals(_characterEncoding)) _writer.write(strValue);
 422  0
             else _writer.write(UnicodeEncoder.encode(strValue) );
 423  
         }
 424  
         else
 425  
         {
 426  0
             _writer.write(org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder.encode(strValue, false, false, !UTF8.equals(_characterEncoding)));
 427  
         }
 428  0
     }
 429  
 
 430  
     public void writeText(char cbuf[], int off, int len) throws IOException
 431  
     {
 432  0
         if (cbuf == null)
 433  
         {
 434  0
             throw new NullPointerException("cbuf name must not be null");
 435  
         }
 436  0
         if (cbuf.length < off + len)
 437  
         {
 438  0
             throw new IndexOutOfBoundsException((off + len) + " > " + cbuf.length);
 439  
         }
 440  
 
 441  0
         closeStartTagIfNecessary();
 442  
 
 443  0
         if (isScriptOrStyle())
 444  
         {
 445  0
             String strValue = new String(cbuf, off, len);
 446  
             // Don't bother encoding anything if chosen character encoding is UTF-8
 447  0
             if (UTF8.equals(_characterEncoding)) _writer.write(strValue);
 448  0
             else _writer.write(UnicodeEncoder.encode(strValue) );
 449  0
         }
 450  0
         else if (isTextarea())
 451  
         {
 452  
             // For textareas we must *not* map successive spaces to &nbsp or Newlines to <br/>
 453  0
             org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder.encode(cbuf, off, len, false, false, !UTF8.equals(_characterEncoding), _writer);
 454  
         }
 455  
         else
 456  
         {
 457  
             // We map successive spaces to &nbsp; and Newlines to <br/>
 458  0
             org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder.encode(cbuf, off, len, true, true, !UTF8.equals(_characterEncoding), _writer);
 459  
         }
 460  0
     }
 461  
 
 462  
     private boolean isScriptOrStyle()
 463  
     {
 464  0
         initializeStartedTagInfo();
 465  
 
 466  0
         return (_isStyle != null && _isStyle.booleanValue()) ||
 467  
                 (_isScript != null && _isScript.booleanValue());
 468  
     }
 469  
 
 470  
     private boolean isScript()
 471  
     {
 472  0
         initializeStartedTagInfo();
 473  
 
 474  0
         return (_isScript != null && _isScript.booleanValue());
 475  
     }
 476  
 
 477  
     private boolean isTextarea()
 478  
     {
 479  0
         initializeStartedTagInfo();
 480  
 
 481  0
         return _isTextArea != null && _isTextArea.booleanValue();
 482  
     }
 483  
 
 484  
     private void initializeStartedTagInfo()
 485  
     {
 486  0
         if(_startElementName != null)
 487  
         {
 488  0
             if(_isScript==null)
 489  
             {
 490  0
                 if(_startElementName.equalsIgnoreCase(HTML.SCRIPT_ELEM))
 491  
                 {
 492  0
                     _isScript = Boolean.TRUE;
 493  0
                     _isStyle = Boolean.FALSE;
 494  0
                     _isTextArea = Boolean.FALSE;
 495  
                 }
 496  
                 else
 497  
                 {
 498  0
                     _isScript = Boolean.FALSE;
 499  
                 }
 500  
             }
 501  0
             if(_isStyle == null)
 502  
             {
 503  0
                 if(_startElementName.equalsIgnoreCase(org.apache.myfaces.shared_impl.renderkit.html.HTML.STYLE_ELEM))
 504  
                 {
 505  0
                     _isStyle = Boolean.TRUE;
 506  0
                     _isTextArea = Boolean.FALSE;
 507  
                 }
 508  
                 else
 509  
                 {
 510  0
                     _isStyle = Boolean.FALSE;
 511  
                 }
 512  
             }
 513  
 
 514  0
             if(_isTextArea == null)
 515  
             {
 516  0
                 if(_startElementName.equalsIgnoreCase(HTML.TEXTAREA_ELEM))
 517  
                 {
 518  0
                     _isTextArea = Boolean.TRUE;
 519  
                 }
 520  
                 else
 521  
                 {
 522  0
                     _isTextArea = Boolean.FALSE;
 523  
                 }
 524  
             }
 525  
         }
 526  0
     }
 527  
 
 528  
     public ResponseWriter cloneWithWriter(Writer writer)
 529  
     {
 530  0
         HtmlResponseWriterImpl newWriter
 531  
                 = new HtmlResponseWriterImpl(writer, getContentType(), getCharacterEncoding());
 532  0
         newWriter._writeDummyForm = _writeDummyForm;
 533  0
         newWriter._dummyFormParams = _dummyFormParams;
 534  0
         return newWriter;
 535  
     }
 536  
 
 537  
 
 538  
     // Writer methods
 539  
 
 540  
     public void close() throws IOException
 541  
     {
 542  0
         closeStartTagIfNecessary();
 543  0
         _writer.close();
 544  0
     }
 545  
 
 546  
     public void write(char cbuf[], int off, int len) throws IOException
 547  
     {
 548  0
         closeStartTagIfNecessary();
 549  0
         String strValue = new String(cbuf, off, len);
 550  
         // Don't bother encoding anything if chosen character encoding is UTF-8
 551  0
         if (UTF8.equals(_characterEncoding)) _writer.write(strValue);
 552  0
         else _writer.write(UnicodeEncoder.encode(strValue) );
 553  0
     }
 554  
 
 555  
     public void write(int c) throws IOException
 556  
     {
 557  0
         closeStartTagIfNecessary();
 558  0
         _writer.write(c);
 559  0
     }
 560  
 
 561  
     public void write(char cbuf[]) throws IOException
 562  
     {
 563  0
         closeStartTagIfNecessary();
 564  0
         String strValue = new String(cbuf);
 565  
         // Don't bother encoding anything if chosen character encoding is UTF-8
 566  0
         if (UTF8.equals(_characterEncoding)) _writer.write(strValue);
 567  0
         else _writer.write(UnicodeEncoder.encode(strValue) );
 568  0
     }
 569  
 
 570  
     public void write(String str) throws IOException
 571  
     {
 572  0
         closeStartTagIfNecessary();
 573  
         // empty string commonly used to force the start tag to be closed.
 574  
         // in such case, do not call down the writer chain
 575  0
         if (str.length() > 0)
 576  
         {
 577  
             // Don't bother encoding anything if chosen character encoding is UTF-8
 578  0
             if (UTF8.equals(_characterEncoding)) _writer.write(str);
 579  0
             else _writer.write(UnicodeEncoder.encode(str) );
 580  
         }
 581  0
     }
 582  
 
 583  
     public void write(String str, int off, int len) throws IOException
 584  
     {
 585  0
         closeStartTagIfNecessary();
 586  0
         String strValue = str.substring(off, off+len);
 587  
         // Don't bother encoding anything if chosen character encoding is UTF-8
 588  0
         if (UTF8.equals(_characterEncoding)) _writer.write(strValue);
 589  0
         else _writer.write(UnicodeEncoder.encode(strValue) );
 590  0
     }
 591  
     
 592  
     /**
 593  
      * This method ignores the <code>UIComponent</code> provided and simply calls
 594  
      * <code>writeText(Object,String)</code>
 595  
      * @since 1.2
 596  
      */
 597  
     public void writeText(Object object, UIComponent component, String string) throws IOException
 598  
     {
 599  0
         writeText(object,string);
 600  0
     }
 601  
 }