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.view.facelets.tag.jsf.html;
20  
21  import java.util.Arrays;
22  import javax.faces.render.Renderer;
23  import javax.faces.view.facelets.FaceletException;
24  import javax.faces.view.facelets.Tag;
25  import javax.faces.view.facelets.TagAttribute;
26  import javax.faces.view.facelets.TagAttributes;
27  import javax.faces.view.facelets.TagDecorator;
28  import org.apache.myfaces.view.facelets.tag.TagAttributeImpl;
29  import org.apache.myfaces.view.facelets.tag.TagAttributesImpl;
30  import org.apache.myfaces.view.facelets.tag.jsf.JsfLibrary;
31  import org.apache.myfaces.view.facelets.tag.jsf.PassThroughLibrary;
32  import org.apache.myfaces.view.facelets.tag.jsf.core.CoreLibrary;
33  
34  /**
35   * Default implementation of TagDecorator as described in JSF 2.2 javadoc of
36   * javax.faces.view.facelets.TagDecorator
37   * 
38   * @since 2.2
39   * @author Leonardo Uribe
40   */
41  public class DefaultTagDecorator implements TagDecorator
42  {
43      public final static String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
44      public final static String JSF_NAMESPACE = JsfLibrary.NAMESPACE;
45      public final static String JSF_ALIAS_NAMESPACE = JsfLibrary.ALIAS_NAMESPACE;
46      public final static String PASS_THROUGH_NAMESPACE = PassThroughLibrary.NAMESPACE;
47      public final static String PASS_THROUGH_ALIAS_NAMESPACE = PassThroughLibrary.ALIAS_NAMESPACE;
48      private final static String EMPTY_NAMESPACE = "";
49      
50      private final static String P_ELEMENTNAME = "p:"+Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY;
51      
52      /**
53       * Fast array for lookup of local names to be inspected. 
54       */
55      static private final Object[][] LOCAL_NAME_ARR = new Object[256][];
56      
57      static private final Object[] A_NAMES = new Object[]
58      {
59        "a", new Object[]
60        {
61          new TagSelectorImpl("jsf:action", "h:commandLink"),
62          new TagSelectorImpl("jsf:actionListener", "h:commandLink"),
63          new TagSelectorImpl("jsf:value", "h:outputLink"),
64          new TagSelectorImpl("jsf:outcome", "h:link")
65        }
66      };
67  
68      static private final Object[] B_NAMES = new Object[]
69      {
70        "body",   new Object[]{new TagSelectorImpl(null, "h:body")},
71        "button", new Object[]{
72            new TagSelectorImpl("jsf:outcome", "h:button"),
73            new TagSelectorImpl(null, "h:commandButton")
74        }
75      };
76      
77      static private final Object[] F_NAMES = new Object[]
78      {
79        "form", new Object[]{new TagSelectorImpl(null, "h:form")}
80      };
81      
82      static private final Object[] H_NAMES = new Object[]
83      {
84        "head", new Object[]{new TagSelectorImpl(null, "h:head")}
85      };
86      
87      static private final Object[] I_NAMES = new Object[]
88      {
89        "img", new Object[]{new TagSelectorImpl(null, "h:graphicImage")},
90        // We can optimize this part, but note the decoration step is done at
91        // compile time, so at the end it does not matter. The important 
92        // optimization is the outer one.      
93        "input", new Object[]{
94            new TagSelectorImpl("type=\"button\"", "h:commandButton"),
95            new TagSelectorImpl("type=\"checkbox\"", "h:selectBooleanCheckbox"),          
96            
97            new TagSelectorImpl("type=\"color\"", "h:inputText"),
98            new TagSelectorImpl("type=\"date\"", "h:inputText"),
99            new TagSelectorImpl("type=\"datetime\"", "h:inputText"),
100           new TagSelectorImpl("type=\"datetime-local\"", "h:inputText"),          
101           new TagSelectorImpl("type=\"email\"", "h:inputText"),
102           new TagSelectorImpl("type=\"month\"", "h:inputText"),
103           new TagSelectorImpl("type=\"number\"", "h:inputText"),
104           new TagSelectorImpl("type=\"range\"", "h:inputText"),
105           new TagSelectorImpl("type=\"search\"", "h:inputText"),
106           new TagSelectorImpl("type=\"time\"", "h:inputText"),
107           new TagSelectorImpl("type=\"url\"", "h:inputText"),
108           new TagSelectorImpl("type=\"week\"", "h:inputText"),
109           
110           new TagSelectorImpl("type=\"file\"", "h:inputFile"),
111           new TagSelectorImpl("type=\"hidden\"", "h:inputHidden"),
112           new TagSelectorImpl("type=\"password\"", "h:inputSecret"),
113           new TagSelectorImpl("type=\"reset\"", "h:commandButton"),
114           new TagSelectorImpl("type=\"submit\"", "h:commandButton"),
115           new TagSelectorImpl("type=\"*\"", "h:inputText")
116       }
117     };
118     
119     static private final Object[] L_NAMES = new Object[]
120     {
121       "label", new Object[]{new TagSelectorImpl(null, "h:outputLabel")},
122       "link",  new Object[]{new TagSelectorImpl(null, "h:outputStylesheet")}
123     };
124 
125     static private final Object[] S_NAMES = new Object[]
126     {
127       "script", new Object[]{new TagSelectorImpl(null, "h:outputScript")},
128       "select", new Object[]
129       {
130         new TagSelectorImpl("multiple=\"*\"", "h:selectManyListbox"),
131         new TagSelectorImpl(null, "h:selectOneListbox")
132       }
133     };
134     
135     static private final Object[] T_NAMES = new Object[]
136     {
137       "textarea", new Object[]{new TagSelectorImpl(null, "h:inputTextarea")}
138     };    
139 
140     static
141     {
142       LOCAL_NAME_ARR['a'] = A_NAMES;
143       LOCAL_NAME_ARR['A'] = A_NAMES;
144       LOCAL_NAME_ARR['b'] = B_NAMES;
145       LOCAL_NAME_ARR['B'] = B_NAMES;
146       LOCAL_NAME_ARR['f'] = F_NAMES;
147       LOCAL_NAME_ARR['F'] = F_NAMES;
148       LOCAL_NAME_ARR['h'] = H_NAMES;
149       LOCAL_NAME_ARR['H'] = H_NAMES;
150       LOCAL_NAME_ARR['i'] = I_NAMES;
151       LOCAL_NAME_ARR['I'] = I_NAMES;
152       LOCAL_NAME_ARR['l'] = L_NAMES;
153       LOCAL_NAME_ARR['L'] = L_NAMES;
154       LOCAL_NAME_ARR['s'] = S_NAMES;
155       LOCAL_NAME_ARR['S'] = S_NAMES;
156       LOCAL_NAME_ARR['t'] = T_NAMES;
157       LOCAL_NAME_ARR['T'] = T_NAMES;
158     }
159     
160     static private final String[][] RESERVED_JSF_ATTRS_ARR =  new String[256][];
161     
162     static private final String[] JSF_ATTRS_B_NAMES = {"binding"};
163 
164     static private final String[] JSF_ATTRS_I_NAMES = {"id"};
165     
166     static private final String[] JSF_ATTRS_R_NAMES = {"rendered"};
167     
168     static private final String[] JSF_ATTRS_T_NAMES = {"transient"};
169 
170     static 
171     {
172       RESERVED_JSF_ATTRS_ARR['b'] = JSF_ATTRS_B_NAMES;
173       RESERVED_JSF_ATTRS_ARR['B'] = JSF_ATTRS_B_NAMES;
174       RESERVED_JSF_ATTRS_ARR['i'] = JSF_ATTRS_I_NAMES;
175       RESERVED_JSF_ATTRS_ARR['I'] = JSF_ATTRS_I_NAMES;
176       RESERVED_JSF_ATTRS_ARR['r'] = JSF_ATTRS_R_NAMES;
177       RESERVED_JSF_ATTRS_ARR['R'] = JSF_ATTRS_R_NAMES;
178       RESERVED_JSF_ATTRS_ARR['t'] = JSF_ATTRS_T_NAMES;
179       RESERVED_JSF_ATTRS_ARR['T'] = JSF_ATTRS_T_NAMES;
180     }
181     
182     private static final TagDecoratorExecutor NO_MATCH_SELECTOR = new TagSelectorImpl(null, "jsf:element");
183     
184     public Tag decorate(Tag tag)
185     {
186         boolean jsfNamespaceFound = false;
187 
188         for (String namespace : tag.getAttributes().getNamespaces())
189         {
190             if (JSF_NAMESPACE.equals(namespace) || JSF_ALIAS_NAMESPACE.equals(namespace))
191             {
192                 jsfNamespaceFound = true;
193                 break;
194             }
195         }
196         if (!jsfNamespaceFound)
197         {
198             // Return null, so the outer CompositeTagDecorator can process the tag.
199             return null;
200         }
201         
202         // One or many attributes has the JSF_NAMESPACE attribute set. Check empty or
203         // xhtml namespace
204         if (EMPTY_NAMESPACE.equals(tag.getNamespace()) ||
205             XHTML_NAMESPACE.equals(tag.getNamespace()))
206         {
207             String localName = tag.getLocalName();
208             boolean processed = false;
209             if (isLocalNameDecorated(localName))
210             {
211                 Object[] array = LOCAL_NAME_ARR[localName.charAt(0)];
212                 int localNameIndex = -1;
213                 if (array != null)
214                 {
215                     for (int i = array.length - 2; i >= 0; i-=2)
216                     {
217                         if (localName.equalsIgnoreCase((String)array[i]))
218                         {
219                             localNameIndex = i;
220                             break;
221                         }
222                     }
223                     if (localNameIndex >= 0)
224                     {
225                         Object[] tagSelectorArray = (Object[]) array[localNameIndex+1];
226 
227                         for (int i = 0; i < tagSelectorArray.length; i++)
228                         {
229                             TagSelector tagSelector = (TagSelector) tagSelectorArray[i];
230                             TagDecoratorExecutor executor = tagSelector.getExecutorIfApplies(tag);
231 
232                             if (executor != null)
233                             {
234                                 return executor.decorate(tag, convertTagAttributes(tag));
235                             }
236                         }
237                     }
238                 }
239             }
240             if (!processed)
241             {
242                 //If no matching entry is found, let jsf:element be the value of targetTag
243                 return NO_MATCH_SELECTOR.decorate(tag, convertTagAttributes(tag));
244             }
245             return null;
246         }
247         else
248         {
249             throw new FaceletException("Attributes under "+JSF_NAMESPACE+
250                 " can only be used for tags under "+ XHTML_NAMESPACE +" or tags with no namespace defined" );
251         }
252     }
253     
254     private TagAttributes convertTagAttributes(Tag tag)
255     {        
256         TagAttribute[] sourceTagAttributes = tag.getAttributes().getAll();
257         
258         String elementNameTagLocalName = tag.getLocalName();
259 
260         TagAttribute elementNameTagAttribute = new TagAttributeImpl(
261             tag.getLocation(), PASS_THROUGH_NAMESPACE , Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY,
262             P_ELEMENTNAME, elementNameTagLocalName );
263         
264         // 1. Count how many attributes requires to be duplicated
265         int duplicateCount = 0;
266         
267         TagAttribute[] convertedTagAttributes = new TagAttribute[
268             sourceTagAttributes.length+1+duplicateCount];
269         boolean elementNameTagAttributeSet = false;
270         int j = 0;
271 
272         for (int i = 0; i < sourceTagAttributes.length; i++)
273         {
274             TagAttribute tagAttribute = sourceTagAttributes[i];
275             String convertedNamespace;
276             String qname;
277             String namespace = tagAttribute.getNamespace();
278             
279             /*
280                 -= Leonardo Uribe =- After check the javadoc and compare it with the code and try some
281                 examples with the implementation done in the RI, we found that the javadoc of 
282                 TagDecorator has some bugs. Below is the description of the implementation done, which
283                 resembles the behavior found on the RI.
284 
285                 "...
286                 For each of argument tag's attributes obtain a reference to a TagAttribute 
287                 with the following characteristics. For discussion let such an attribute be 
288                 convertedTagAttribute.
289 
290                     * convertedTagAttribute's location: from the argument tag's location.
291 
292                     * If the current attribute's namespace is http://xmlns.jcp.org/jsf, 
293                         convertedTagAttribute's qualified name must be the current attribute's 
294                         local name and convertedTagAttribute's namespace must be the empty string. 
295                         This will have the effect of setting the current attribute as a proper 
296                         property on the UIComponent instance represented by this markup.
297 
298                     * If the current attribute's namespace is empty, assume the current 
299                         attribute's namespace is http://xmlns.jcp.org/jsf/passthrough. 
300                         ConvertedTagAttribute's qualified name is the current attribute's 
301                         local name prefixed by "p:". convertedTagAttribute's namespace must be 
302                         http://xmlns.jcp.org/jsf/passthrough.
303 
304                     * Otherwise, if the current attribute's namespace is not empty, let 
305                         the current attribute be convertedTagAttribute. This will have the 
306                         effect of let the attribute be processed by the meta rules defined
307                         by the TagHandler instance associated with the generated target 
308                         component.
309                 ..."        
310             */            
311             if (JSF_NAMESPACE.equals(namespace) || JSF_ALIAS_NAMESPACE.equals(namespace))
312             {
313                 // "... If the current attribute's namespace is http://xmlns.jcp.org/jsf, convertedTagAttribute's 
314                 //  qualified name must be the current attribute's local name and convertedTagAttribute's 
315                 // namespace must be the empty string. This will have the effect of setting the current 
316                 // attribute as a proper property on the UIComponent instance represented by this markup.
317                 convertedNamespace = "";
318                 qname = tagAttribute.getLocalName();
319                 
320                 convertedTagAttributes[j] = new TagAttributeImpl(tagAttribute.getLocation(), 
321                     convertedNamespace, tagAttribute.getLocalName(), qname, tagAttribute.getValue());
322             }
323             else if (namespace == null)
324             {
325                 // should not happen, but let it because org.xml.sax.Attributes considers it
326                 // -= Leonardo Uribe =- after conversation with Frank Caputo, who was the main contributor for
327                 // this feature in JSF 2.2, he said that if the namespace is empty the intention is pass the
328                 // attribute to the passthrough attribute map, so there is an error in the spec documentation.
329                 //convertedTagAttributes[j] = tagAttribute;
330                 
331                 convertedNamespace = PASS_THROUGH_NAMESPACE;
332                 qname = "p:"+tagAttribute.getLocalName();
333                 
334                 convertedTagAttributes[j] = new TagAttributeImpl(tagAttribute.getLocation(), 
335                     convertedNamespace, tagAttribute.getLocalName(), qname, tagAttribute.getValue());
336             }
337             else if (tagAttribute.getNamespace().length() == 0)
338             {
339                 // "... If the current attribute's namespace is empty 
340                 // let the current attribute be convertedTagAttribute. ..."
341                 // -= Leonardo Uribe =- after conversation with Frank Caputo, who was the main contributor for
342                 // this feature in JSF 2.2, he said that if the namespace is empty the intention is pass the
343                 // attribute to the passthrough attribute map, so there is an error in the spec documentation.
344                 //convertedTagAttributes[j] = tagAttribute;
345                 
346                 convertedNamespace = PASS_THROUGH_NAMESPACE;
347                 qname = "p:"+tagAttribute.getLocalName();
348                 
349                 convertedTagAttributes[j] = new TagAttributeImpl(tagAttribute.getLocation(), 
350                     convertedNamespace, tagAttribute.getLocalName(), qname, tagAttribute.getValue());
351             }
352             else /*if (!tag.getNamespace().equals(tagAttribute.getNamespace()))*/
353             {
354                 // "... or different from the argument tag's namespace, 
355                 // let the current attribute be convertedTagAttribute. ..."
356                 convertedTagAttributes[j] = tagAttribute;
357             }
358             
359             if (Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY.equals(convertedTagAttributes[j].getLocalName()) && (
360                 PASS_THROUGH_NAMESPACE.equals(convertedTagAttributes[j].getNamespace()) || 
361                 PASS_THROUGH_ALIAS_NAMESPACE.equals(convertedTagAttributes[j].getNamespace()) ) )
362             {
363                 elementNameTagAttributeSet = true;
364             }
365             j++;
366         }
367         
368         if (elementNameTagAttributeSet)
369         {
370             // This is unlikely, but theorically possible.
371             return new TagAttributesImpl(Arrays.copyOf(convertedTagAttributes, convertedTagAttributes.length-1));
372         }
373         else
374         {
375             convertedTagAttributes[convertedTagAttributes.length-1] = elementNameTagAttribute;
376             return new TagAttributesImpl(convertedTagAttributes);
377         }
378     }
379     
380     private boolean isLocalNameDecorated(String elem)
381     {
382         Object[] array = LOCAL_NAME_ARR[elem.charAt(0)];
383         if (array != null)
384         {
385             for (int i = array.length - 2; i >= 0; i-=2)
386             {
387                 if (elem.equalsIgnoreCase((String)array[i]))
388                 {
389                     return true;
390                 }
391             }
392         }
393         return false;
394     }
395 
396     private boolean isReservedJSFAttribute(String attr)
397     {
398         String[] array = RESERVED_JSF_ATTRS_ARR[attr.charAt(0)];
399         if (array != null)
400         {
401             for (int i = array.length - 1; i >= 0; i-=1)
402             {
403                 if (attr.equalsIgnoreCase((String)array[i]))
404                 {
405                     return true;
406                 }
407             }
408         }
409         return false;
410     }
411     
412     private static interface TagDecoratorExecutor
413     {
414         public Tag decorate(Tag orig, TagAttributes attributes);
415         
416     }
417     
418     private static abstract class TagSelector
419     {
420         public abstract TagDecoratorExecutor getExecutorIfApplies(Tag tag);
421     }
422     
423     private static class TagSelectorImpl extends TagSelector implements TagDecoratorExecutor
424     {
425         //private String selector;
426         private String attributeQName;
427         private String attributeLocalName;
428         private String attributePrefix;
429         private final String attributeNamespace;
430         private final String attributeAliasNamespace;
431         private String matchValue;
432         
433         private String targetQName;
434         private String targetNamespace;
435         private String targetLocalName;
436         
437         public TagSelectorImpl(String selector, String targetQName)
438         {
439             // The idea in this constructor is do the parsing step of the selector
440             // just once, so the check can be done quickly.
441             //this.selector = selector;
442             if (selector != null)
443             {
444                 int i = selector.indexOf('=');
445                 if (i >= 0)
446                 {
447                     this.attributeQName = selector.substring(0,i);
448                     String value = selector.substring(i+1);
449                     int s = value.indexOf('"');
450                     int t = value.lastIndexOf('"');
451                     if (s >= 0 && t >= 0 && t > s)
452                     {
453                         this.matchValue = value.substring(s+1,t);
454                     }
455                     else
456                     {
457                         this.matchValue = value;
458                     }
459                 }
460                 else
461                 {
462                     this.attributeQName = selector;
463                     this.matchValue = null;
464                 }
465                 
466                 int j = attributeQName.indexOf(':');
467                 this.attributeLocalName = (j >= 0) ? attributeQName.substring(j+1) : attributeQName;
468                 this.attributePrefix = (j >= 0) ? attributeQName.substring(0, j) : null;
469                 this.attributeNamespace = resolveSelectorNamespace(this.attributePrefix);
470                 this.attributeAliasNamespace = resolveAliasSelectorNamespace(this.attributePrefix);
471             }
472             else
473             {
474                 this.attributeQName = null;
475                 this.matchValue = null;
476                 this.attributeLocalName = null;
477                 this.attributePrefix = null;
478                 this.attributeNamespace = "";
479                 this.attributeAliasNamespace = null;
480             }
481             
482             this.targetQName = targetQName;
483             if (targetQName != null)
484             {
485                 int j = targetQName.indexOf(':');
486                 if (j >= 0)
487                 {
488                     //this.
489                     if (j == 1 && targetQName.charAt(0) == 'h')
490                     {
491                         this.targetNamespace = HtmlLibrary.NAMESPACE;
492                         this.targetLocalName = targetQName.substring(j+1);
493                     }
494                     else if (j == 3 && targetQName.startsWith("jsf"))
495                     {
496                         this.targetNamespace = JsfLibrary.ALIAS_NAMESPACE;
497                         this.targetLocalName = targetQName.substring(j+1);
498                     }
499                 }
500                 else
501                 {
502                     this.targetLocalName = targetQName;
503                 }
504             }
505         }
506 
507         public TagDecoratorExecutor getExecutorIfApplies(Tag tag)
508         {
509             if (attributeQName != null)
510             {
511                  if (matchValue != null)
512                  {
513                      String attributeNS = attributeNamespace;
514                      TagAttribute attr = tag.getAttributes().get(attributeNS, attributeLocalName);
515                      if (attr == null && attributeAliasNamespace.length() > 0)
516                      {
517                          attributeNS = attributeAliasNamespace;
518                          attr = tag.getAttributes().get(attributeAliasNamespace, attributeLocalName);
519                      }
520                      if (attr != null)
521                      {
522                          if (attributeNS.equals(attr.getNamespace()) )
523                          {
524                             // if namespace is the same match
525                              if (matchValue.equals(attr.getValue()))
526                              {
527                                 return this;
528                              }
529                              else if ("*".equals(matchValue) && attr.getValue() != null)
530                              {
531                                  return this;
532                              }
533                          }
534                          else if (attributeNS == "" && attr.getNamespace() == null)
535                          {
536                              // if namespace is empty match
537                              if (matchValue.equals(attr.getValue()))
538                              {
539                                  return this;
540                              }
541                              else if ("*".equals(matchValue) && attr.getValue() != null)
542                              {
543                                  return this;
544                              }
545                          }
546                      }
547                  }
548                  else
549                  {
550                      String attributeNS = attributeNamespace;
551                      TagAttribute attr = tag.getAttributes().get(attributeNS, attributeLocalName);
552                      if (attr == null)
553                      {
554                          attributeNS = attributeAliasNamespace;
555                          attr = tag.getAttributes().get(attributeNS, attributeLocalName);
556                      }
557                      if (attr != null)
558                      {
559                          if (attributeNS.equals(attr.getNamespace()))
560                          {
561                              // if namespace is the same match
562                              return this;
563                          }
564                          else if (attributeNS == "" && attr.getNamespace() == null)
565                          {
566                              // if namespace is empty match
567                              return this;
568                          }
569                      }
570                  }
571                 return null;
572             }
573             else
574             {
575                 return this;
576             }
577         }
578         
579         public Tag decorate(Tag orig, TagAttributes attributes)
580         {
581             return new Tag(orig.getLocation(), this.targetNamespace, 
582                 this.targetLocalName, this.targetQName, attributes);
583         }
584     }
585 
586     private static String resolveSelectorNamespace(String prefix)
587     {
588         if ("jsf".equals(prefix))
589         {
590             return JsfLibrary.NAMESPACE;
591         }
592         else if ("h".equals(prefix))
593         {
594             return HtmlLibrary.NAMESPACE;
595         }
596         else if ("f".equals(prefix))
597         {
598             return CoreLibrary.NAMESPACE;
599         }
600         return "";
601     }
602 
603     private static String resolveAliasSelectorNamespace(String prefix)
604     {
605         if ("jsf".equals(prefix))
606         {
607             return JsfLibrary.ALIAS_NAMESPACE;
608         }
609         else if ("h".equals(prefix))
610         {
611             return HtmlLibrary.ALIAS_NAMESPACE;
612         }
613         else if ("f".equals(prefix))
614         {
615             return CoreLibrary.ALIAS_NAMESPACE;
616         }
617         return "";
618     }
619 }