Save This Page
Home » mojarra-1.2_09-b02-FCS-source » com.sun.faces.renderkit » [javadoc | source]
    1   /*
    2    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    3    * 
    4    * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
    5    * 
    6    * The contents of this file are subject to the terms of either the GNU
    7    * General Public License Version 2 only ("GPL") or the Common Development
    8    * and Distribution License("CDDL") (collectively, the "License").  You
    9    * may not use this file except in compliance with the License. You can obtain
   10    * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
   11    * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
   12    * language governing permissions and limitations under the License.
   13    * 
   14    * When distributing the software, include this License Header Notice in each
   15    * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
   16    * Sun designates this particular file as subject to the "Classpath" exception
   17    * as provided by Sun in the GPL Version 2 section of the License file that
   18    * accompanied this code.  If applicable, add the following below the License
   19    * Header, with the fields enclosed by brackets [] replaced by your own
   20    * identifying information: "Portions Copyrighted [year]
   21    * [name of copyright owner]"
   22    * 
   23    * Contributor(s):
   24    * 
   25    * If you wish your version of this file to be governed by only the CDDL or
   26    * only the GPL Version 2, indicate your decision by adding "[Contributor]
   27    * elects to include this software in this distribution under the [CDDL or GPL
   28    * Version 2] license."  If you don't indicate a single choice of license, a
   29    * recipient has the option to distribute your version of this file under
   30    * either the CDDL, the GPL Version 2 or to extend the choice of license to
   31    * its licensees as provided above.  However, if you add GPL Version 2 code
   32    * and therefore, elected the GPL Version 2 license, then the option applies
   33    * only if the new code is made subject to such option by the copyright
   34    * holder.
   35    */
   36   
   37   package com.sun.faces.renderkit;
   38   
   39   import java.io.BufferedReader;
   40   import java.io.IOException;
   41   import java.io.InputStream;
   42   import java.io.InputStreamReader;
   43   import java.io.StringReader;
   44   import java.io.StringWriter;
   45   import java.io.Writer;
   46   import java.net.URL;
   47   import java.net.URLConnection;
   48   import java.util.ArrayList;
   49   import java.util.Arrays;
   50   import java.util.Collection;
   51   import java.util.List;
   52   import java.util.Map;
   53   import java.util.Map.Entry;
   54   import java.util.logging.Level;
   55   import java.util.logging.Logger;
   56   import java.math.BigDecimal;
   57   
   58   import javax.faces.FacesException;
   59   import javax.faces.FactoryFinder;
   60   import javax.faces.component.UIComponent;
   61   import javax.faces.component.UIComponentBase;
   62   import javax.faces.component.UISelectItem;
   63   import javax.faces.component.UISelectItems;
   64   import javax.faces.context.ExternalContext;
   65   import javax.faces.context.FacesContext;
   66   import javax.faces.context.ResponseWriter;
   67   import javax.faces.model.SelectItem;
   68   import javax.faces.render.RenderKit;
   69   import javax.faces.render.RenderKitFactory;
   70   import javax.faces.render.ResponseStateManager;
   71   
   72   import com.sun.faces.RIConstants;
   73   import com.sun.faces.config.WebConfiguration;
   74   import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter;
   75   import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.Param;
   76   import com.sun.faces.util.FacesLogger;
   77   import com.sun.faces.util.MessageUtils;
   78   import com.sun.faces.util.Util;
   79   import com.sun.faces.util.RequestStateManager;
   80   
   81   /**
   82    * <p>A set of utilities for use in {@link RenderKit}s.</p>
   83    */
   84   public class RenderKitUtils {
   85   
   86       /**
   87        * <p>The prefix to append to certain attributes when renderking
   88        * <code>XHTML Transitional</code> content.
   89        */
   90       private static final String XHTML_ATTR_PREFIX = "xml:";
   91   
   92       
   93       /**
   94        * <p><code>Boolean</code> attributes to be rendered 
   95        * using <code>XHMTL</code> semantics.
   96        */
   97       private static final String[] BOOLEAN_ATTRIBUTES = {
   98             "disabled", "ismap", "readonly"
   99       };
  100      
  101   
  102       /**
  103        * <p>An array of attributes that must be prefixed by
  104        * {@link #XHTML_ATTR_PREFIX} when rendering 
  105        * <code>XHTML Transitional</code> content.
  106        */
  107       private static final String[] XHTML_PREFIX_ATTRIBUTES = {
  108             "lang"
  109       };
  110   
  111       /**
  112        * <p>The maximum number of array elements that can be used
  113        * to hold content types from an accept String.</p>
  114        */ 
  115       private final static int MAX_CONTENT_TYPES = 50;
  116   
  117       /**
  118        * <p>The maximum number of content type parts.
  119        * For example: for the type: "text/html; level=1; q=0.5"
  120        * The parts of this type would be:
  121        *      "text" - type
  122        *      "html; level=1" - subtype
  123        *      "0.5" - quality value
  124        *      "1" - level value </p> 
  125        */ 
  126       private final static int MAX_CONTENT_TYPE_PARTS = 4;
  127                                                                                                                            
  128       /**
  129        * The character that is used to delimit content types
  130        * in an accept String.</p>
  131        */
  132       private final static String CONTENT_TYPE_DELIMITER = ",";
  133                                                                                                                            
  134       /**
  135        * The character that is used to delimit the type and 
  136        * subtype portions of a content type in an accept String.
  137        * Example: text/html </p>
  138        */
  139       private final static String CONTENT_TYPE_SUBTYPE_DELIMITER = "/";
  140   
  141       /**
  142        * <p>JavaScript to be rendered when a commandLink is used.
  143        * This may be expaned to include other uses.</p>
  144        */
  145       private static final String SUN_JSF_JS = RIConstants.FACES_PREFIX + "sunJsfJs";
  146   
  147   
  148       /**
  149        * This array represents the packages that can leverage the
  150        * <code>attributesThatAreSet</code> List for optimized attribute
  151        * rendering.
  152        *
  153        * IMPLEMENTATION NOTE:  This must be kept in sync with the array
  154        * in UIComponentBase$AttributesMap and HtmlComponentGenerator.
  155        *
  156        * Hopefully JSF 2.0 will remove the need for this.
  157        */
  158       private static final String[] OPTIMIZED_PACKAGES = {
  159             "javax.faces.component",
  160             "javax.faces.component.html"
  161       };
  162       static {
  163           // Sort the array for use with Arrays.binarySearch()
  164           Arrays.sort(OPTIMIZED_PACKAGES);
  165       }
  166   
  167   
  168       /**
  169        * IMPLEMENTATION NOTE:  This must be kept in sync with the Key
  170        * in UIComponentBase$AttributesMap and HtmlComponentGenerator.
  171        *
  172        * Hopefully JSF 2.0 will remove the need for this.
  173        */
  174       private static final String ATTRIBUTES_THAT_ARE_SET_KEY =
  175           UIComponentBase.class.getName() + ".attributesThatAreSet";
  176                             
  177       
  178       protected static final Logger LOGGER = FacesLogger.RENDERKIT.getLogger();
  179             
  180   
  181       // ------------------------------------------------------------ Constructors
  182   
  183   
  184       private RenderKitUtils() {
  185       }
  186   
  187   
  188       // ---------------------------------------------------------- Public Methods
  189   
  190   
  191       /**
  192        * <p>Return the {@link RenderKit} for the current request.</p>
  193        * @param context the {@link FacesContext} of the current request
  194        * @return the {@link RenderKit} for the current request.
  195        */
  196       public static RenderKit getCurrentRenderKit(FacesContext context) {
  197   
  198           RenderKitFactory renderKitFactory = (RenderKitFactory)
  199                 FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
  200           return renderKitFactory.getRenderKit(context,
  201                                                context
  202                                                      .getViewRoot().getRenderKitId());
  203   
  204       }
  205   
  206   
  207       /**
  208        * <p>Obtain and return the {@link ResponseStateManager} for
  209        * the specified #renderKitId.</p>
  210        * 
  211        * @param context the {@link FacesContext} of the current request
  212        * @param renderKitId {@link RenderKit} ID
  213        * @return the {@link ResponseStateManager} for the specified
  214        *  #renderKitId
  215        * @throws FacesException if an exception occurs while trying
  216        *  to obtain the <code>ResponseStateManager</code>
  217        */
  218       public static ResponseStateManager getResponseStateManager(
  219             FacesContext context, String renderKitId)
  220             throws FacesException {
  221   
  222           assert (null != renderKitId);
  223           assert (null != context);
  224   
  225           RenderKit renderKit = context.getRenderKit();
  226           if (renderKit == null) {
  227               // check request scope for a RenderKitFactory implementation            
  228               RenderKitFactory factory = (RenderKitFactory)
  229                     RequestStateManager.get(context, RequestStateManager.RENDER_KIT_IMPL_REQ);
  230               if (factory != null) {
  231                   renderKit = factory.getRenderKit(context, renderKitId);
  232               } else {
  233                   factory = (RenderKitFactory)
  234                         FactoryFinder
  235                               .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
  236                   if (factory == null) {
  237                       throw new IllegalStateException();
  238                   } else {
  239                       RequestStateManager.set(context,
  240                                               RequestStateManager.RENDER_KIT_IMPL_REQ,
  241                                               factory);
  242                   }
  243                   renderKit = factory.getRenderKit(context, renderKitId);
  244               }
  245           }
  246           return renderKit.getResponseStateManager();
  247   
  248       }
  249       
  250       /**
  251        * <p>Return a List of {@link javax.faces.model.SelectItem}
  252        * instances representing the available options for this component, 
  253        * assembled from the set of {@link javax.faces.component.UISelectItem} 
  254        * and/or {@link javax.faces.component.UISelectItems} components that are
  255        * direct children of this component.  If there are no such children, an
  256        * empty <code>Iterator</code> is returned.</p>
  257        *
  258        * @param context The {@link javax.faces.context.FacesContext} for the current request.
  259        *                If null, the UISelectItems behavior will not work.
  260        * @param component the component
  261        * @throws IllegalArgumentException if <code>context</code>
  262        *                                  is <code>null</code>
  263        * @return a List of the select items for the specified component
  264        */
  265       public static List<SelectItem> getSelectItems(FacesContext context,
  266                                                     UIComponent component) {
  267   
  268           if (context == null) {            
  269               throw new IllegalArgumentException(
  270                   MessageUtils.getExceptionMessageString(
  271                       MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context"));
  272           }
  273           
  274           ArrayList<SelectItem> list = new ArrayList<SelectItem>();
  275           for (UIComponent kid : component.getChildren()) {
  276               if (kid instanceof UISelectItem) {
  277                   UISelectItem item = (UISelectItem) kid;
  278                   Object value = item.getValue();
  279                   
  280                   if (value == null) {
  281                       list.add(new SelectItem(item.getItemValue(),
  282                                               item.getItemLabel(),
  283                                               item.getItemDescription(),
  284                                               item.isItemDisabled(),
  285                                               item.isItemEscaped()));
  286                   } else if (value instanceof SelectItem) {
  287                       list.add((SelectItem) value);
  288                   } else {
  289                       throw new IllegalArgumentException(
  290                           MessageUtils.getExceptionMessageString(
  291                               MessageUtils.VALUE_NOT_SELECT_ITEM_ID,
  292                               component.getId(),
  293                               value.getClass().getName()));
  294                   }
  295               } else if (kid instanceof UISelectItems) {
  296                   Object value = ((UISelectItems) kid).getValue();
  297                   if (value instanceof SelectItem) {
  298                       list.add((SelectItem) value);
  299                   } else if (value instanceof SelectItem[]) {
  300                       SelectItem[] items = (SelectItem[]) value;
  301                       // we manually copy the elements so that the list is
  302                       // modifiable.  Arrays.asList() returns a non-mutable
  303                       // list.
  304                       //noinspection ManualArrayToCollectionCopy
  305                       for (SelectItem item : items) {
  306                           list.add(item);
  307                       }
  308                   } else if (value instanceof Collection) {
  309                       for (Object element : ((Collection) value)) {
  310                           if (SelectItem.class.isInstance(element)) {
  311                               list.add((SelectItem) element);
  312                           } else {
  313                               throw new IllegalArgumentException(
  314                                     MessageUtils.getExceptionMessageString(
  315                                           MessageUtils.VALUE_NOT_SELECT_ITEM_ID,
  316                                           component.getId(),
  317                                           value.getClass().getName()));
  318                           }
  319                       }
  320                   } else if (value instanceof Map) {
  321                       Map optionMap = (Map) value;
  322                       for (Object o : optionMap.entrySet()) {
  323                           Entry entry = (Entry) o;
  324                           Object key = entry.getKey();
  325                           Object val = entry.getValue();
  326                           if (key == null || val == null) {
  327                               continue;
  328                           }
  329                           list.add(new SelectItem(val, 
  330                                                   key.toString()));
  331                       }
  332                   } else {
  333                       throw new IllegalArgumentException(
  334                             MessageUtils.getExceptionMessageString(
  335                               MessageUtils.CHILD_NOT_OF_EXPECTED_TYPE_ID,
  336                               "UISelectItem/UISelectItems",
  337                               component.getFamily(),
  338                               component.getId(),
  339                               value != null ? value.getClass().getName() : "null"));
  340                   }
  341               }
  342           }
  343           return (list);
  344   
  345       }
  346   
  347   
  348       /**
  349        * <p>Render any "passthru" attributes, where we simply just output the
  350        * raw name and value of the attribute.  This method is aware of the
  351        * set of HTML4 attributes that fall into this bucket.  Examples are
  352        * all the javascript attributes, alt, rows, cols, etc. </p>
  353        * 
  354        * @param writer writer the {@link javax.faces.context.ResponseWriter} to be used when writing
  355        *  the attributes
  356        * @param component the component
  357        * @param attributes an array off attributes to be processed
  358        * @throws IOException if an error occurs writing the attributes
  359        */
  360       public static void renderPassThruAttributes(ResponseWriter writer,
  361                                                   UIComponent component,
  362                                                   String[] attributes)
  363       throws IOException {
  364   
  365           assert (null != writer);
  366           assert (null != component);
  367   
  368           Map<String, Object> attrMap = component.getAttributes();
  369   
  370           // PENDING - think anyone would run the RI using another implementation
  371           // of the jsf-api?  If they did, then this would fall apart.  That
  372           // scenario seems extremely unlikely.
  373           if (canBeOptimized(component)) {
  374               //noinspection unchecked
  375               List<String> setAttributes = (List<String>)
  376                 component.getAttributes().get(ATTRIBUTES_THAT_ARE_SET_KEY);
  377               if (setAttributes != null) {
  378                   renderPassThruAttributesOptimized(writer,
  379                                                     component,
  380                                                     attributes,
  381                                                     setAttributes);
  382               }
  383           } else {
  384               // this block should only be hit by custom components leveraging
  385               // the RI's rendering code.  We make no assumptions and loop through
  386               // all known attributes.
  387               boolean isXhtml =
  388                     RIConstants.XHTML_CONTENT_TYPE.equals(writer.getContentType());
  389               for (String attrName : attributes) {
  390   
  391                   Object value =
  392                         attrMap.get(attrName);
  393                   if (value != null && shouldRenderAttribute(value)) {
  394                       writer.writeAttribute(prefixAttribute(attrName, isXhtml),
  395                                             value,
  396                                             attrName);
  397                   }
  398               }
  399   
  400           }
  401       }
  402   
  403   
  404       public static String prefixAttribute(final String attrName,
  405                                            final ResponseWriter writer) {
  406   
  407           return (prefixAttribute(attrName,
  408                RIConstants.XHTML_CONTENT_TYPE.equals(writer.getContentType())));
  409   
  410       }
  411   
  412   
  413       public static String prefixAttribute(final String attrName,
  414                                            boolean isXhtml) {
  415           if (isXhtml) {
  416               if (Arrays.binarySearch(XHTML_PREFIX_ATTRIBUTES, attrName) > -1) {
  417                   return XHTML_ATTR_PREFIX + attrName;
  418               } else {
  419                   return attrName;
  420               }
  421           } else {
  422               return attrName;
  423           }
  424           
  425       }
  426   
  427   
  428       /**
  429        * <p>Renders the attributes from {@link #BOOLEAN_ATTRIBUTES} 
  430        * using <code>XHMTL</code> semantics (i.e., disabled="disabled").</p>
  431        * 
  432        * @param writer writer the {@link ResponseWriter} to be used when writing
  433        *  the attributes
  434        * @param component the component
  435        * @throws IOException if an error occurs writing the attributes
  436        */
  437       public static void renderXHTMLStyleBooleanAttributes(ResponseWriter writer,
  438                                                            UIComponent component)
  439             throws IOException {
  440   
  441           assert (writer != null);
  442           assert (component != null);
  443   
  444           Map attrMap = component.getAttributes();
  445           for (String attrName : BOOLEAN_ATTRIBUTES) {
  446               Object val = attrMap.get(attrName);
  447               if (val == null) {
  448                   continue;
  449               }
  450   
  451               if (Boolean.valueOf(val.toString())) {
  452                   writer.writeAttribute(attrName,
  453                                         true,
  454                                         attrName);
  455               }
  456           }
  457   
  458       }
  459   
  460       /**
  461        * <p>Given an accept String from the client, and a <code>String</code>
  462        * of server supported content types, determine the best qualified 
  463        * content type for the client.  If no match is found, or either of the 
  464        * arguments are <code>null</code>,  <code>null</code> is returned.</p>
  465        *
  466        * @param accept The client accept String
  467        * @param serverSupportedTypes The types that the server supports
  468        * @param preferredType The preferred content type if another type is found
  469        *        with the same highest quality factor.
  470        * @return The content type <code>String</code>
  471        */
  472       public static String determineContentType(String accept, String serverSupportedTypes, String preferredType) {
  473           String contentType = null;
  474                                                                                                                            
  475           if (null == accept || null == serverSupportedTypes) {
  476               return contentType;
  477           }
  478                                                                                                                            
  479           String[][] clientContentTypes = buildTypeArrayFromString(accept);
  480           String[][] serverContentTypes = buildTypeArrayFromString(serverSupportedTypes);
  481           String[][] preferredContentType = buildTypeArrayFromString(preferredType);
  482           String[][] matchedInfo = findMatch(clientContentTypes, serverContentTypes, preferredContentType);
  483                                                                                                                            
  484           // if best match exits and best match is not some wildcard,
  485           // return best match
  486           if ((matchedInfo[0][1] != null) && !(matchedInfo[0][2].equals("*"))) {
  487               contentType = matchedInfo[0][1] + CONTENT_TYPE_SUBTYPE_DELIMITER + matchedInfo[0][2];
  488           }
  489                                                                                                                            
  490           return contentType;
  491       }
  492       
  493       
  494       /**
  495        * @param contentType the content type in question
  496        * @return <code>true</code> if the content type is a known XML-based
  497        *  content type, otherwise, <code>false</code>
  498        */
  499       public static boolean isXml(String contentType) {
  500           return (RIConstants.XHTML_CONTENT_TYPE.equals(contentType)
  501                   || RIConstants.APPLICATION_XML_CONTENT_TYPE.equals(contentType)
  502                   || RIConstants.TEXT_XML_CONTENT_TYPE.equals(contentType));
  503       }
  504   
  505   
  506       // --------------------------------------------------------- Private Methods
  507   
  508   
  509       /**
  510        * @param component the UIComponent in question
  511        * @return <code>true</code> if the component is within the
  512        *  <code>javax.faces.component</code> or <code>javax.faces.component.html</code>
  513        *  packages, otherwise return <code>false</code>
  514        */
  515       private static boolean canBeOptimized(UIComponent component) {
  516   
  517           Package p = component.getClass().getPackage();
  518           return (p != null && (Arrays
  519                 .binarySearch(OPTIMIZED_PACKAGES, p.getName()) >= 0));
  520   
  521       }
  522   
  523   
  524       /**
  525        * <p>For each attribute in <code>setAttributes</code>, perform a binary
  526        * search against the array of <code>knownAttributes</code>  If a match is found
  527        * and the value is not <code>null</code>, render the attribute.
  528        * @param writer the current writer
  529        * @param component the component whose attributes we're rendering
  530        * @param knownAttributes an array of pass-through attributes supported by
  531        *  this component
  532        * @param setAttributes a <code>List</code> of attributes that have been set
  533        *  on the provided component
  534        * @throws IOException if an error occurs during the write
  535        */
  536       private static void renderPassThruAttributesOptimized(ResponseWriter writer,
  537                                                             UIComponent component,
  538                                                             String[] knownAttributes,
  539                                                             List<String> setAttributes)
  540       throws IOException {
  541   
  542           String[] attributes = setAttributes.toArray(new String[setAttributes.size()]);
  543           Arrays.sort(attributes);
  544           boolean isXhtml =
  545                 RIConstants.XHTML_CONTENT_TYPE.equals(writer.getContentType());
  546           Map<String, Object> attrMap = component.getAttributes();
  547           for (String name : attributes) {
  548               if (Arrays.binarySearch(knownAttributes, name) >= 0) {
  549                   Object value =
  550                         attrMap.get(name);
  551                   if (value != null && shouldRenderAttribute(value)) {
  552                       writer.writeAttribute(prefixAttribute(name, isXhtml),
  553                                             value,
  554                                             name);
  555                   }
  556               }
  557           }
  558   
  559       }
  560   
  561   
  562       /**
  563        * <p>Determines if an attribute should be rendered based on the
  564        * specified #attributeVal.</p>
  565        * 
  566        * @param attributeVal the attribute value
  567        * @return <code>true</code> if and only if #attributeVal is
  568        *  an instance of a wrapper for a primitive type and its value is
  569        *  equal to the default value for that type as given in the specification.
  570        */
  571       private static boolean shouldRenderAttribute(Object attributeVal) {
  572   
  573           if (attributeVal instanceof String) {
  574               return true;
  575           } else if (attributeVal instanceof Boolean &&
  576               Boolean.FALSE.equals(attributeVal)) {
  577               return false;
  578           } else if (attributeVal instanceof Integer &&
  579                      (Integer) attributeVal == Integer.MIN_VALUE) {
  580               return false;
  581           } else if (attributeVal instanceof Double &&
  582                      (Double) attributeVal == Double.MIN_VALUE) {
  583               return false;
  584           } else if (attributeVal instanceof Character &&
  585                      (Character) attributeVal
  586                      == Character
  587                            .MIN_VALUE) {
  588               return false;
  589           } else if (attributeVal instanceof Float &&
  590                      (Float) attributeVal == Float.MIN_VALUE) {
  591               return false;
  592           } else if (attributeVal instanceof Short &&
  593                      (Short) attributeVal == Short.MIN_VALUE) {
  594               return false;
  595           } else if (attributeVal instanceof Byte &&
  596                      (Byte) attributeVal == Byte.MIN_VALUE) {
  597               return false;
  598           } else if (attributeVal instanceof Long &&
  599                      (Long) attributeVal == Long.MIN_VALUE) {
  600               return false;
  601           }
  602           return true;
  603   
  604       }
  605   
  606       /**
  607        * <p>This method builds a two element array structure as follows:
  608        * Example:
  609        *     Given the following accept string:
  610        *       text/html; level=1, text/plain; q=0.5
  611        *     [0][0] 1  (quality is 1 if none specified)
  612        *     [0][1] "text"  (type)
  613        *     [0][2] "html; level=1" (subtype)
  614        *     [0][3] 1 (level, if specified; null if not)
  615        *
  616        *     [1][0] .5
  617        *     [1][1] "text"
  618        *     [1][2] "plain"
  619        *     [1][3] (level, if specified; null if not)
  620        *
  621        * The array is used for comparison purposes in the findMatch method.</p>
  622        *
  623        * @param accept An accept <code>String</code>
  624        * @return an two dimensional array containing content-type/quality info
  625        */
  626       private static String[][] buildTypeArrayFromString(String accept) {
  627           String[][] arrayAccept = new String[MAX_CONTENT_TYPES][MAX_CONTENT_TYPE_PARTS];
  628           // return if empty
  629           if ((accept == null) || (accept.length() == 0))
  630               return arrayAccept;
  631           // some helper variables
  632           StringBuilder typeSubType;
  633           String type;
  634           String subtype;
  635           String level = null;
  636           String quality = null;
  637   
  638           // Parse "types"        
  639           String[] types = Util.split(accept, CONTENT_TYPE_DELIMITER);
  640           int index = -1;
  641           for (int i=0; i<types.length; i++) {
  642               String token = types[i].trim();
  643               index += 1;
  644               // Check to see if our accept string contains the delimiter that is used
  645               // to add uniqueness to a type/subtype, and/or delimits a qualifier value:
  646               //    Example: text/html;level=1,text/html;level=2; q=.5
  647               if (token.contains(";")) {                
  648                   String[] typeParts = Util.split(token, ";");
  649                   typeSubType = new StringBuilder(typeParts[0].trim());
  650                   for (int j=1; j<typeParts.length; j++) {
  651                       quality = "not set";
  652                       token = typeParts[j].trim();
  653                       // if "level" is present, make sure it gets included in the "type/subtype"
  654                       if (token.contains("level")) {
  655                           typeSubType.append(';').append(token);
  656                           String[] levelParts = Util.split(token, "=");                        
  657                           level = levelParts[0].trim();
  658                           if (level.equalsIgnoreCase("level")) {
  659                               level = levelParts[1].trim();
  660                           }
  661                       } else {
  662                           quality = token;
  663                           String[] qualityParts = Util.split(quality, "=");                       
  664                           quality = qualityParts[0].trim();
  665                           if (quality.equalsIgnoreCase("q")) {
  666                               quality = qualityParts[1].trim();
  667                               break;
  668                           } else {
  669                               quality = "not set"; // to identifiy that no quality was supplied
  670                           }
  671                       }
  672                   }
  673               } else {
  674                   typeSubType = new StringBuilder(token);
  675                   quality = "not set"; // to identifiy that no quality was supplied
  676               }
  677               // now split type and subtype
  678               if (typeSubType.indexOf(CONTENT_TYPE_SUBTYPE_DELIMITER) >= 0) {               
  679                   String[] typeSubTypeParts = Util.split(typeSubType.toString(), CONTENT_TYPE_SUBTYPE_DELIMITER);
  680                   type = typeSubTypeParts[0].trim();
  681                   subtype = typeSubTypeParts[1].trim();
  682               } else {
  683                   type = typeSubType.toString();
  684                   subtype = "";
  685               }
  686               // check quality and assign values
  687               if ("not set".equals(quality)) {
  688                   if (type.equals("*") && subtype.equals("*")) {
  689                       quality = "0.01";
  690                   } else if (!type.equals("*") && subtype.equals("*")) {
  691                       quality = "0.02";
  692                   } else if (type.equals("*") && subtype.length() == 0) {
  693                       quality = "0.01";
  694                   } else {
  695                       quality = "1";
  696                   }
  697               }
  698               arrayAccept[index][0] = quality;
  699               arrayAccept[index][1] = type;
  700               arrayAccept[index][2] = subtype;
  701               arrayAccept[index][3] = level;
  702           }
  703           return (arrayAccept);
  704       }
  705   
  706       /**
  707        * <p>For each server supported type, compare client (browser) specified types.
  708        * If a match is found, keep track of the highest quality factor.
  709        * The end result is that for all matches, only the one with the highest
  710        * quality will be returned.</p>
  711        *
  712        * @param clientContentTypes An <code>array</code> of accept <code>String</code>
  713        * information for the client built from @{link #buildTypeArrayFromString}. 
  714        * @param serverSupportedContentTypes An <code>array</code> of accept <code>String</code>
  715        * information for the server supported types built from @{link #buildTypeArrayFromString}. 
  716        * @param preferredContentType An <code>array</code> of preferred content type information.
  717        * @return An <code>array</code> containing the parts of the preferred content type for the
  718        * client.  The information is stored as outlined in @{link #buildTypeArrayFromString}. 
  719        */
  720       private static String[][] findMatch(String[][] clientContentTypes,
  721                                           String[][] serverSupportedContentTypes,
  722                                           String[][] preferredContentType) {
  723   
  724           // result array
  725           String[][] results = new String[MAX_CONTENT_TYPES][MAX_CONTENT_TYPE_PARTS];
  726           int resultidx = -1;
  727           // the highest quality
  728           double highestQFactor = 0;
  729           // the record with the highest quality
  730           int idx = 0;
  731           for (int sidx = 0; sidx < MAX_CONTENT_TYPES; sidx++) {
  732               // get server type
  733               String serverType = serverSupportedContentTypes[sidx][1];
  734               if (serverType != null) {
  735                   for (int cidx = 0; cidx < MAX_CONTENT_TYPES; cidx++) {
  736                       // get browser type
  737                       String browserType = clientContentTypes[cidx][1];
  738                       if (browserType != null) {
  739                           // compare them and check for wildcard
  740                           if ((browserType.equalsIgnoreCase(serverType)) || (browserType.equals("*"))) {
  741                               // types are equal or browser type is wildcard - compare subtypes
  742                               if ((clientContentTypes[cidx][2].equalsIgnoreCase(
  743                                   serverSupportedContentTypes[sidx][2])) ||
  744                                   (clientContentTypes[cidx][2].equals("*"))) {
  745                                   // subtypes are equal or browser subtype is wildcard
  746                                   // found match: multiplicate qualities and add to result array
  747                                   // if there was a level associated, this gets higher precedence, so
  748                                   // factor in the level in the calculation.
  749                                   double cLevel = 0.0;
  750                                   double sLevel = 0.0;
  751                                   if (clientContentTypes[cidx][3] != null) {
  752                                       cLevel = (Double.parseDouble(clientContentTypes[cidx][3]))*.10;
  753                                   }
  754                                   if (serverSupportedContentTypes[sidx][3] != null) {
  755                                       sLevel = (Double.parseDouble(serverSupportedContentTypes[sidx][3]))*.10;
  756                                   }
  757                                   double cQfactor = Double.parseDouble(clientContentTypes[cidx][0]) + cLevel;
  758                                   double sQfactor = Double.parseDouble(serverSupportedContentTypes[sidx][0]) + sLevel;
  759                                   double resultQuality = cQfactor * sQfactor;
  760                                   resultidx += 1;
  761                                   results[resultidx][0] = String.valueOf(resultQuality);
  762                                   if (clientContentTypes[cidx][2].equals("*")) {
  763                                       // browser subtype is wildcard
  764                                       // return type and subtype (wildcard)
  765                                       results[resultidx][1] = clientContentTypes[cidx][1];
  766                                       results[resultidx][2] = clientContentTypes[cidx][2];
  767                                   } else {
  768                                       // return server type and subtype
  769                                       results[resultidx][1] = serverSupportedContentTypes[sidx][1];
  770                                       results[resultidx][2] = serverSupportedContentTypes[sidx][2];
  771                                       results[resultidx][3] = serverSupportedContentTypes[sidx][3];
  772                                   }
  773                                   // check if this was the highest factor
  774                                   if (resultQuality > highestQFactor) {
  775                                       idx = resultidx;
  776                                       highestQFactor = resultQuality;
  777                                   }
  778                               }
  779                           }
  780                       }
  781                   }
  782               }
  783           }
  784   
  785           // First, determine if we have a type that has the highest quality factor that
  786           // also matches the preferred type (if there is one):
  787           String[][] match = new String[1][3];
  788           if (preferredContentType[0][0] != null) {
  789               BigDecimal highestQual = BigDecimal.valueOf(highestQFactor);
  790               for (int i=0; i<=resultidx; i++) {
  791                   if ((BigDecimal.valueOf(Double.parseDouble(results[i][0])).compareTo(highestQual) == 0) &&
  792                       (results[i][1]).equals(preferredContentType[0][1]) &&
  793                       (results[i][2]).equals(preferredContentType[0][2])) {
  794                       match[0][0] = results[i][0];
  795                       match[0][1] = results[i][1];
  796                       match[0][2] = results[i][2];
  797                       return match;
  798                   }
  799               }
  800           }
  801                   
  802           match[0][0] = results[idx][0];
  803           match[0][1] = results[idx][1];
  804           match[0][2] = results[idx][2];
  805           return match;
  806       }
  807   
  808       /**
  809        * <p>Replaces all occurrences of <code>-</code> with <code>$_</code>.</p>
  810        * 
  811        * @param origIdentifier the original identifer that needs to be
  812        *  'ECMA-ized'
  813        * @return an ECMA valid identifer
  814        */
  815       public static String createValidECMAIdentifier(String origIdentifier) {
  816           return origIdentifier.replace("-", "$_");
  817       }
  818   
  819   
  820       /**
  821        * <p>Renders the Javascript necessary to add and remove request
  822        * parameters to the current form.</p>
  823        * @param writer the <code>ResponseWriter</code>
  824        * @param context the <code>FacesContext</code> for the current request
  825        * @throws java.io.IOException if an error occurs writing to the response
  826        */
  827       public static void renderFormInitScript(ResponseWriter writer,
  828                                               FacesContext context)
  829             throws IOException {
  830           WebConfiguration webConfig =
  831                 WebConfiguration.getInstance(context.getExternalContext());
  832   
  833           if (webConfig.isOptionEnabled(BooleanWebContextInitParameter.ExternalizeJavaScript)) {
  834               // PENDING
  835               // We need to look into how to make this work in a portlet environment.
  836               // For the time being, this feature will need to be disabled when running
  837               // in a portlet.
  838               String mapping = Util.getFacesMapping(context);
  839               String uri;
  840               if ((mapping != null) && (Util.isPrefixMapped(mapping))) {
  841                   uri = mapping + '/' + RIConstants.SUN_JSF_JS_URI;
  842               } else {
  843                   uri = '/' + RIConstants.SUN_JSF_JS_URI + mapping;
  844               }
  845               writer.write('\n');
  846               writer.startElement("script", null);
  847               writer.writeAttribute("type", "text/javascript", null);
  848               writer.writeAttribute("src",
  849                                     context.getExternalContext()
  850                                           .getRequestContextPath() + uri,
  851                                     null);
  852               writer.endElement("script");
  853               writer.write("\n");
  854           } else {           
  855               writer.write('\n');
  856               writer.startElement("script", null);
  857               writer.writeAttribute("type", "text/javascript", null);
  858               writer.writeAttribute("language", "Javascript", null);           
  859               writeSunJS(context, writer);
  860               writer.endElement("script");
  861               writer.write("\n");
  862           }
  863       }
  864   
  865       /**
  866        * <p>Returns a string that can be inserted into the <code>onclick</code>
  867        * handler of a command.  This string will add all request parameters
  868        * as well as the client ID of the activated command to the form as
  869        * hidden input parameters, update the target of the link if necessary,
  870        * and handle the form submission.  The content of {@link #SUN_JSF_JS}
  871        * must be rendered prior to using this method.</p>
  872        * @param formClientId the client ID of the form
  873        * @param commandClientId the client ID of the command
  874        * @param target the link target
  875        * @param params the nested parameters, if any @return a String suitable for the <code>onclick</code> handler
  876        *  of a command
  877        * @return the default <code>onclick</code> JavaScript for the default
  878        *  command link component
  879        */
  880       public static String getCommandLinkOnClickScript(String formClientId,
  881                                                        String commandClientId,
  882                                                        String target,
  883                                                        Param[] params) {
  884   
  885           StringBuilder sb = new StringBuilder(256);    
  886           sb.append("if(typeof jsfcljs == 'function'){jsfcljs(document.getElementById('");
  887           sb.append(formClientId);        
  888           sb.append("'),{'");
  889           sb.append(commandClientId).append("':'").append(commandClientId);
  890           for (Param param : params) {
  891               String pn = param.name;
  892               if (pn != null && pn.length() != 0) {
  893                   String pv = param.value;
  894                   sb.append("','");
  895                   sb.append(pn.replace("'", "\\\'"));
  896                   sb.append("':'");
  897                   if (pv != null && pv.length() != 0) {
  898                       sb.append(pv.replace("'", "\\\'"));
  899                   }
  900               }
  901           }
  902           sb.append("'},'");
  903           sb.append(target);
  904           sb.append("');}return false");
  905   
  906           return sb.toString();
  907           
  908       }
  909   
  910   
  911       /**
  912        * <p>This is a utility method for compressing multi-lined javascript.
  913        * In the case of {@link #SUN_JSF_JS} it offers about a 47% decrease
  914        * in length.</p>
  915        * 
  916        * <p>For our purposes, compression is just trimming each line and 
  917        * then writing it out.  It's pretty simplistic, but it works.</p>
  918        * 
  919        * @param JSString the string to compress
  920        * @return the compressed string
  921        */
  922       public static char[] compressJS(String JSString) {
  923   
  924           BufferedReader reader = new BufferedReader(new StringReader(JSString));
  925           StringWriter writer = new StringWriter(1024);          
  926           try {
  927               for (String line = reader.readLine();
  928                    line != null;
  929                    line = reader.readLine()) {
  930   
  931                   line = line.trim();
  932                   writer.write(line);
  933               }
  934               return writer.toString().toCharArray();
  935           } catch (IOException ioe) {
  936               // won't happen
  937           }
  938           return null;
  939   
  940       }
  941   
  942   
  943       /**
  944        * <p>Return the implementation JavaScript.  If compression
  945        * is enabled, the result will be compressed.</p>
  946        * 
  947        * @param context - the <code>FacesContext</code> for the current request
  948        * @param writer - the <code>Writer</code> to write the JS to
  949        * @throws IOException if the JavaScript cannot be written
  950        * 
  951        */
  952       public static void writeSunJS(FacesContext context, Writer writer)
  953       throws IOException {   
  954           writer.write((char[]) context.getExternalContext().getApplicationMap()
  955                 .get(SUN_JSF_JS));
  956       }
  957       
  958       
  959       // --------------------------------------------------------- Private Methods
  960   
  961   
  962       /**
  963        * <p>Loads the contents of the sunjsf.js file into memory removing any
  964        * comments/empty lines it encoutners, and, if enabled, compressing the
  965        * result.</p>  This method should only be called when the application is
  966        * being initialized.
  967        * @param extContext the ExternalContext for this application
  968        */
  969       public synchronized static void loadSunJsfJs(ExternalContext extContext) {
  970           Map<String, Object> appMap =
  971                extContext.getApplicationMap();
  972           char[] sunJsfJs;
  973   
  974           BufferedReader reader = null;
  975           try {
  976               URL url = Util.getCurrentLoader(appMap)
  977                    .getResource("com/sun/faces/sunjsf.js");
  978               if (url == null) {
  979                   LOGGER.severe(
  980                        "jsf.renderkit.util.cannot_load_js");
  981                   return;
  982               }
  983               URLConnection conn = url.openConnection();
  984               conn.setUseCaches(false);
  985               InputStream input = conn.getInputStream();
  986               reader = new BufferedReader(
  987                    new InputStreamReader(input));
  988               StringBuilder builder = new StringBuilder(128);
  989               for (String line = reader.readLine();
  990                    line != null;
  991                    line = reader.readLine()) {
  992   
  993                   String temp = line.trim();
  994                   if (temp.length() == 0
  995                        || temp.startsWith("/*")
  996                        || temp.startsWith("*")
  997                        || temp.startsWith("*/")
  998                        || temp.startsWith("//")) {
  999                       continue;
 1000                   }
 1001                   builder.append(line).append('\n');
 1002               }
 1003               builder.deleteCharAt(builder.length() - 1);
 1004               if (WebConfiguration
 1005                    .getInstance(extContext)
 1006                    .isOptionEnabled(BooleanWebContextInitParameter.CompressJavaScript)) {
 1007                   sunJsfJs = compressJS(builder.toString());
 1008               } else {
 1009                   sunJsfJs = builder.toString().toCharArray();
 1010               }
 1011               appMap.put(SUN_JSF_JS, sunJsfJs);
 1012           } catch (IOException ioe) {
 1013               LOGGER.log(Level.SEVERE,
 1014                    "jsf.renderkit.util.cannot_load_js",
 1015                    ioe);
 1016           } finally {
 1017               if (reader != null) {
 1018                   try {
 1019                       reader.close();
 1020                   } catch (IOException ioe) {
 1021                       // ignore
 1022                   }
 1023               }
 1024           }
 1025       }
 1026                              
 1027   } // END RenderKitUtils

Save This Page
Home » mojarra-1.2_09-b02-FCS-source » com.sun.faces.renderkit » [javadoc | source]