View Javadoc

    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_orchestra.renderkit.html;
    20
    21  import org.apache.myfaces.shared_orchestra.renderkit.JSFAttr;
    22  import org.apache.myfaces.shared_orchestra.renderkit.RendererUtils;
    23  import org.apache.myfaces.shared_orchestra.renderkit.html.util.FormInfo;
    24  import org.apache.myfaces.shared_orchestra.renderkit.html.util.JavascriptUtils;
    25  import org.apache.myfaces.shared_orchestra.config.MyfacesConfig;
    26
    27  import javax.faces.application.StateManager;
    28  import javax.faces.application.ViewHandler;
    29  import javax.faces.component.UICommand;
    30  import javax.faces.component.UIComponent;
    31  import javax.faces.component.UIOutput;
    32  import javax.faces.component.UIParameter;
    33  import javax.faces.component.html.HtmlCommandLink;
    34  import javax.faces.context.FacesContext;
    35  import javax.faces.context.ResponseWriter;
    36  import javax.faces.event.ActionEvent;
    37  import java.io.IOException;
    38  import java.io.UnsupportedEncodingException;
    39  import java.net.URLEncoder;
    40  import java.util.Iterator;
    41
    42  /**
    43   * @author Manfred Geiler
    44   * @version $Revision: 947902 $ $Date: 2010-05-24 22:39:45 -0500 (Mon, 24 May 2010) $
    45   */
    46  public abstract class HtmlLinkRendererBase
    47      extends HtmlRenderer {
    48      public static final String URL_STATE_MARKER = "JSF_URL_STATE_MARKER=DUMMY";
    49      public static final int URL_STATE_MARKER_LEN = URL_STATE_MARKER.length();
    50
    51      //private static final Log log = LogFactory.getLog(HtmlLinkRenderer.class);
    52
    53      public boolean getRendersChildren() {
    54          // We must be able to render the children without a surrounding anchor
    55          // if the Link is disabled
    56          return true;
    57      }
    58
    59      public void decode(FacesContext facesContext, UIComponent component) {
    60          super.decode(facesContext, component);  //check for NP
    61
    62          if (component instanceof UICommand) {
    63              String clientId = component.getClientId(facesContext);
    64              FormInfo formInfo = findNestingForm(component, facesContext);
    65              String reqValue = (String) facesContext.getExternalContext().getRequestParameterMap().get(
    66                  HtmlRendererUtils.getHiddenCommandLinkFieldName(formInfo));
    67              if (reqValue != null && reqValue.equals(clientId)) {
    68                  component.queueEvent(new ActionEvent(component));
    69
    70                  RendererUtils.initPartialValidationAndModelUpdate(component, facesContext);
    71              }
    72          }
    73          else if (component instanceof UIOutput) {
    74              //do nothing
    75          }
    76          else {
    77              throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
    78          }
    79      }
    80
    81
    82      public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException {
    83          super.encodeBegin(facesContext, component);  //check for NP
    84
    85          if (component instanceof UICommand) {
    86              renderCommandLinkStart(facesContext, component,
    87                                     component.getClientId(facesContext),
    88                                     ((UICommand) component).getValue(),
    89                                     getStyle(facesContext, component),
    90                                     getStyleClass(facesContext, component));
    91          }
    92          else if (component instanceof UIOutput) {
    93              renderOutputLinkStart(facesContext, (UIOutput) component);
    94          }
    95          else {
    96              throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
    97          }
    98      }
    99
    100
    101     /**
    102      * Can be overwritten by derived classes to overrule the style to be used.
    103      */
    104     protected String getStyle(FacesContext facesContext, UIComponent link) {
    105         if (link instanceof HtmlCommandLink) {
    106             return ((HtmlCommandLink) link).getStyle();
    107         }
    108         else {
    109             return (String) link.getAttributes().get(HTML.STYLE_ATTR);
    110         }
    111     }
    112
    113     /**
    114      * Can be overwritten by derived classes to overrule the style class to be used.
    115      */
    116     protected String getStyleClass(FacesContext facesContext, UIComponent link) {
    117         if (link instanceof HtmlCommandLink) {
    118             return ((HtmlCommandLink) link).getStyleClass();
    119         }
    120         else {
    121             return (String) link.getAttributes().get(HTML.STYLE_CLASS_ATTR);
    122         }
    123     }
    124
    125     public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException {
    126         RendererUtils.renderChildren(facesContext, component);
    127     }
    128
    129     public void encodeEnd(FacesContext facesContext, UIComponent component) throws IOException {
    130         super.encodeEnd(facesContext, component);  //check for NP
    131
    132         if (component instanceof UICommand) {
    133             renderCommandLinkEnd(facesContext, component);
    134
    135             HtmlFormRendererBase.renderScrollHiddenInputIfNecessary(
    136                 findNestingForm(component, facesContext).getForm(), facesContext, facesContext.getResponseWriter());
    137         }
    138         else if (component instanceof UIOutput) {
    139             renderOutputLinkEnd(facesContext, component);
    140         }
    141         else {
    142             throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
    143         }
    144     }
    145
    146     protected void renderCommandLinkStart(FacesContext facesContext, UIComponent component,
    147                                           String clientId,
    148                                           Object value,
    149                                           String style,
    150                                           String styleClass)
    151         throws IOException {
    152         ResponseWriter writer = facesContext.getResponseWriter();
    153
    154         String[] anchorAttrsToRender;
    155         if (JavascriptUtils.isJavascriptAllowed(facesContext.getExternalContext())) {
    156             renderJavaScriptAnchorStart(facesContext, writer, component, clientId);
    157             anchorAttrsToRender = HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_ONCLICK_WITHOUT_STYLE;
    158         }
    159         else {
    160             renderNonJavaScriptAnchorStart(facesContext, writer, component, clientId);
    161             anchorAttrsToRender = HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_STYLE;
    162         }
    163
    164         writer.writeAttribute(HTML.ID_ATTR, clientId, null);
    165         HtmlRendererUtils.renderHTMLAttributes(writer, component,
    166                                                anchorAttrsToRender);
    167         HtmlRendererUtils.renderHTMLAttribute(writer, HTML.STYLE_ATTR, HTML.STYLE_ATTR,
    168                                               style);
    169         HtmlRendererUtils.renderHTMLAttribute(writer, HTML.STYLE_CLASS_ATTR, HTML.STYLE_CLASS_ATTR,
    170                                               styleClass);
    171
    172         // render value as required by JSF 1.1 renderkitdocs
    173         if (value != null) {
    174             writer.writeText(value.toString(), JSFAttr.VALUE_ATTR);
    175         }
    176     }
    177
    178     protected void renderJavaScriptAnchorStart(FacesContext facesContext,
    179                                                ResponseWriter writer,
    180                                                UIComponent component,
    181                                                String clientId)
    182         throws IOException {
    183         //Find form
    184         FormInfo formInfo = findNestingForm(component, facesContext);
    185         if (formInfo == null) {
    186             String path = RendererUtils.getPathToComponent(component);
    187             String msg = "Link is not embedded in a form. Change component/tag '" + clientId + "' from javax.faces.*/<h:tagName /> " +
    188                 "to org.apache.myfaces.*/<t:tagName />, or embed it in a form.  This is not a bug. " +
    189                 "Please see: http://wiki.apache.org/myfaces/Upgrading_to_Tomahawk_1.1.3 " +
    190                 "The path to this component is " + path + ". If you need to render a special form and a JSF-form's attributes are not enough," +
    191                 "consider using the s:form tag of the MyFaces sandbox.";
    192             throw new IllegalArgumentException(msg);
    193         }
    194         UIComponent nestingForm = formInfo.getForm();
    195         String formName = formInfo.getFormName();
    196
    197         StringBuffer onClick = new StringBuffer();
    198
    199         String commandOnclick;
    200         if (component instanceof HtmlCommandLink) {
    201             commandOnclick = ((HtmlCommandLink) component).getOnclick();
    202         }
    203         else {
    204             commandOnclick = (String) component.getAttributes().get(HTML.ONCLICK_ATTR);
    205         }
    206         if (commandOnclick != null) {
    207             onClick.append(commandOnclick);
    208             onClick.append(';');
    209         }
    210
    211         if (RendererUtils.isAdfOrTrinidadForm(formInfo.getForm())) {
    212             onClick.append("submitForm('");
    213             onClick.append(formInfo.getForm().getClientId(facesContext));
    214             onClick.append("',1,{source:'");
    215             onClick.append(component.getClientId(facesContext));
    216             onClick.append("'");
    217             // See Jira Issue 1809 https://issues.apache.org/jira/browse/MYFACES-1809
    218             onClick.append(getChildParametersTrinidad(component));
    219             onClick.append("});return false;");
    220         }
    221         else {
    222             HtmlRendererUtils.renderFormSubmitScript(facesContext);
    223
    224             StringBuffer params = addChildParameters(component, nestingForm);
    225
    226             String target = getTarget(component);
    227
    228             onClick.append("return ").
    229                 append(HtmlRendererUtils.SUBMIT_FORM_FN_NAME).append("('").
    230                 append(formName).append("','").
    231                 append(clientId).append("'");
    232
    233             if (params.length() > 2 || target != null) {
    234                 onClick.append(",").
    235                     append(target == null ? "null" : ("'" + target + "'")).append(",").
    236                     append(params);
    237             }
    238             onClick.append(");");
    239
    240
    241             //render hidden field - todo: in here for backwards compatibility
    242             String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(formInfo);
    243             addHiddenCommandParameter(facesContext, nestingForm, hiddenFieldName);
    244
    245             //render hidden field - todo: in here for backwards compatibility
    246             String hiddenFieldNameMyFacesOld = HtmlRendererUtils.getHiddenCommandLinkFieldNameMyfacesOld(formInfo);
    247             addHiddenCommandParameter(facesContext, nestingForm, hiddenFieldNameMyFacesOld);
    248         }
    249
    250         writer.startElement(HTML.ANCHOR_ELEM, component);
    251         writer.writeURIAttribute(HTML.HREF_ATTR, "#", null);
    252         writer.writeAttribute(HTML.ONCLICK_ATTR, onClick.toString(), null);
    253     }
    254
    255     private String getTarget(UIComponent component) {
    256         // for performance reason: double check for the target attribute
    257         String target;
    258         if (component instanceof HtmlCommandLink) {
    259             target = ((HtmlCommandLink) component).getTarget();
    260         }
    261         else {
    262             target = (String) component.getAttributes().get(HTML.TARGET_ATTR);
    263         }
    264         return target;
    265     }
    266
    267    /**
    268     * See Jira Issue 1809 https://issues.apache.org/jira/browse/MYFACES-1809
    269     */
    270     private String getChildParametersTrinidad(UIComponent component) {
    271         //https://issues.apache.org/jira/browse/MYFACES-1809//add child parameters
    272         StringBuffer params = new StringBuffer();
    273         for (Iterator it = getChildren(component).iterator(); it.hasNext();) {
    274
    275             UIComponent child = (UIComponent) it.next();
    276             if (child instanceof UIParameter) {
    277                 String name = ((UIParameter) child).getName();
    278
    279                 if (name == null) {
    280                    throw new IllegalArgumentException("Unnamed parameter value not allowed within command link.");
    281                 }
    282
    283                 Object value = ((UIParameter) child).getValue();
    284
    285                 //UIParameter is no ValueHolder, so no conversion possible - calling .toString on value....
    286                 String strParamValue = value != null ? org.apache.myfaces.shared_orchestra.renderkit.html.util.HTMLEncoder.encode(value.toString(), false, false) : "";
    287
    288                 params.append(",'");
    289
    290                 params.append(name);
    291                 params.append("':'");
    292                 params.append(strParamValue);
    293                 params.append("'");
    294             }
    295         }
    296         return params.toString();
    297     }
    298
    299     private StringBuffer addChildParameters(UIComponent component, UIComponent nestingForm) {
    300         //add child parameters
    301         StringBuffer params = new StringBuffer();
    302         params.append("[");
    303         for (Iterator it = getChildren(component).iterator(); it.hasNext();) {
    304
    305             UIComponent child = (UIComponent) it.next();
    306             if (child instanceof UIParameter) {
    307                 String name = ((UIParameter) child).getName();
    308
    309                 if (name == null) {
    310                     throw new IllegalArgumentException("Unnamed parameter value not allowed within command link.");
    311                 }
    312
    313                 addHiddenCommandParameter(FacesContext.getCurrentInstance(), nestingForm, name);
    314
    315                 Object value = ((UIParameter) child).getValue();
    316
    317                 //UIParameter is no ValueHolder, so no conversion possible - calling .toString on value....
    318                 // MYFACES-1832 bad charset encoding for f:param
    319                 // if HTMLEncoder.encode is called, then
    320                 // when is called on writer.writeAttribute, encode method
    321                 // is called again so we have a duplicated encode
    322                 // call.
    323                 // MYFACES-2726 All '\' and "'" chars must be escaped
    324                 // because there will be inside "'" javascript quotes,
    325                 // otherwise the value will not correctly restored when
    326                 // the command is post.
    327                 //String strParamValue = value != null ? org.apache.myfaces.shared_orchestra.renderkit.html.util.HTMLEncoder.encode(value.toString(), false, false) : "";
    328                 String strParamValue = "";
    329                 if (value != null)
    330                 {
    331                     strParamValue = value.toString();
    332                     StringBuffer buff = null;
    333                     for (int i = 0; i < strParamValue.length(); i++)
    334                     {
    335                         char c = strParamValue.charAt(i);
    336                         if (c == '\'' || c == '\\')
    337                         {
    338                             if (buff == null)
    339                             {
    340                                 buff = new StringBuffer();
    341                                 buff.append(strParamValue.substring(0,i));
    342                             }
    343                             buff.append('\\');
    344                             buff.append(c);
    345                         }
    346                         else if (buff != null)
    347                         {
    348                             buff.append(c);
    349                         }
    350                     }
    351                     if (buff != null)
    352                     {
    353                         strParamValue = buff.toString();
    354                     }
    355                 }
    356
    357                 if (params.length() > 1) {
    358                     params.append(",");
    359                 }
    360
    361                 params.append("['");
    362                 params.append(name);
    363                 params.append("','");
    364                 params.append(strParamValue);
    365                 params.append("']");
    366             }
    367         }
    368         params.append("]");
    369         return params;
    370     }
    371
    372     /**
    373      * find nesting form<br />
    374      * need to be overrideable to deal with dummyForm stuff in tomahawk.
    375      */
    376     protected FormInfo findNestingForm(UIComponent uiComponent, FacesContext facesContext) {
    377         return RendererUtils.findNestingForm(uiComponent, facesContext);
    378     }
    379
    380     protected void addHiddenCommandParameter(FacesContext facesContext, UIComponent nestingForm, String hiddenFieldName) {
    381         if (nestingForm != null) {
    382             HtmlFormRendererBase.addHiddenCommandParameter(facesContext, nestingForm, hiddenFieldName);
    383         }
    384     }
    385
    386
    387     protected void renderNonJavaScriptAnchorStart(FacesContext facesContext,
    388                                                   ResponseWriter writer,
    389                                                   UIComponent component,
    390                                                   String clientId)
    391         throws IOException {
    392         ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
    393         String viewId = facesContext.getViewRoot().getViewId();
    394         String path = viewHandler.getActionURL(facesContext, viewId);
    395
    396         boolean strictXhtmlLinks
    397                 = MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isStrictXhtmlLinks();
    398
    399         StringBuffer hrefBuf = new StringBuffer(path);
    400
    401         //add clientId parameter for decode
    402
    403         if (path.indexOf('?') == -1) {
    404             hrefBuf.append('?');
    405         }
    406         else {
    407             if (strictXhtmlLinks) {
    408                 hrefBuf.append("&amp;");
    409             } else {
    410                 hrefBuf.append('&');
    411             }
    412         }
    413         String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(findNestingForm(component, facesContext));
    414         hrefBuf.append(hiddenFieldName);
    415         hrefBuf.append('=');
    416         hrefBuf.append(clientId);
    417
    418         if (getChildCount(component) > 0) {
    419             addChildParametersToHref(facesContext, component, hrefBuf,
    420                                      false, //not the first url parameter
    421                                      writer.getCharacterEncoding());
    422         }
    423
    424         StateManager stateManager = facesContext.getApplication().getStateManager();
    425         if (strictXhtmlLinks) {
    426             hrefBuf.append("&amp;");
    427         } else {
    428             hrefBuf.append('&');
    429         }
    430         if (stateManager.isSavingStateInClient(facesContext)) {
    431             hrefBuf.append(URL_STATE_MARKER);
    432         }
    433         String href = facesContext.getExternalContext().encodeActionURL(hrefBuf.toString());
    434         writer.startElement(HTML.ANCHOR_ELEM, component);
    435         writer.writeURIAttribute(HTML.HREF_ATTR,
    436                                  facesContext.getExternalContext().encodeActionURL(href),
    437                                  null);
    438     }
    439
    440     private void addChildParametersToHref(FacesContext facesContext,
    441                                           UIComponent linkComponent,
    442                                           StringBuffer hrefBuf,
    443                                           boolean firstParameter,
    444                                           String charEncoding) throws IOException {
    445         boolean strictXhtmlLinks
    446                 = MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isStrictXhtmlLinks();
    447         for (Iterator it = getChildren(linkComponent).iterator(); it.hasNext();) {
    448             UIComponent child = (UIComponent) it.next();
    449             if (child instanceof UIParameter) {
    450                 String name = ((UIParameter) child).getName();
    451                 Object value = ((UIParameter) child).getValue();
    452                 addParameterToHref(name, value, hrefBuf, firstParameter, charEncoding, strictXhtmlLinks);
    453                 firstParameter = false;
    454             }
    455         }
    456     }
    457
    458     protected void renderOutputLinkStart(FacesContext facesContext, UIOutput output)
    459         throws IOException {
    460         ResponseWriter writer = facesContext.getResponseWriter();
    461
    462         //calculate href
    463         String href = org.apache.myfaces.shared_orchestra.renderkit.RendererUtils.getStringValue(facesContext, output);
    464         //check if there is an anchor # in it
    465         int index = href.indexOf('#');
    466         String anchorString = null;
    467         if (index > -1)
    468         {
    469             // remove anchor element and add it again after the parameter are encoded
    470             anchorString = href.substring(index,href.length());
    471             href = href.substring(0,index);
    472         }
    473         if (getChildCount(output) > 0) {
    474             StringBuffer hrefBuf = new StringBuffer(href);
    475             addChildParametersToHref(facesContext, output, hrefBuf,
    476                                      (href.indexOf('?') == -1), //first url parameter?
    477                                      writer.getCharacterEncoding());
    478             href = hrefBuf.toString();
    479         }
    480         if (index > -1)
    481         {
    482             href += anchorString;
    483         }
    484         href = facesContext.getExternalContext().encodeResourceURL(href);    //TODO: or encodeActionURL ?
    485
    486         String clientId = output.getClientId(facesContext);
    487
    488         //write anchor
    489         writer.startElement(HTML.ANCHOR_ELEM, output);
    490         writer.writeAttribute(HTML.ID_ATTR, clientId, null);
    491         writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
    492         writer.writeURIAttribute(HTML.HREF_ATTR, href, null);
    493         HtmlRendererUtils.renderHTMLAttributes(writer, output, org.apache.myfaces.shared_orchestra.renderkit.html.HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
    494         writer.flush();
    495     }
    496
    497     private static void addParameterToHref(String name,
    498                                            Object value,
    499                                            StringBuffer hrefBuf,
    500                                            boolean firstParameter,
    501                                            String charEncoding,
    502                                            boolean strictXhtmlLinks) throws UnsupportedEncodingException {
    503         if (name == null) {
    504             throw new IllegalArgumentException("Unnamed parameter value not allowed within command link.");
    505         }
    506
    507         if (firstParameter) {
    508             hrefBuf.append('?');
    509         } else {
    510             if (strictXhtmlLinks) {
    511                 hrefBuf.append("&amp;");
    512             } else {
    513                 hrefBuf.append('&');
    514             }
    515         }
    516
    517         hrefBuf.append(URLEncoder.encode(name, charEncoding));
    518         hrefBuf.append('=');
    519         if (value != null) {
    520             //UIParameter is no ConvertibleValueHolder, so no conversion possible
    521             hrefBuf.append(URLEncoder.encode(value.toString(), charEncoding));
    522         }
    523     }
    524
    525
    526     protected void renderOutputLinkEnd(FacesContext facesContext, UIComponent component)
    527         throws IOException {
    528         ResponseWriter writer = facesContext.getResponseWriter();
    529         // force separate end tag
    530         writer.writeText("", null);
    531         writer.endElement(HTML.ANCHOR_ELEM);
    532     }
    533
    534     protected void renderCommandLinkEnd(FacesContext facesContext, UIComponent component)
    535         throws IOException {
    536         renderOutputLinkEnd(facesContext, component);
    537     }
    538
    539 }