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.composite;
20  
21  import java.beans.IntrospectionException;
22  import java.beans.PropertyDescriptor;
23  import java.io.IOException;
24  import java.util.List;
25  import java.util.logging.Level;
26  import java.util.logging.Logger;
27  
28  import javax.faces.application.ProjectStage;
29  import javax.faces.component.UIComponent;
30  import javax.faces.context.FacesContext;
31  import javax.faces.view.facelets.FaceletContext;
32  import javax.faces.view.facelets.TagAttribute;
33  import javax.faces.view.facelets.TagConfig;
34  import javax.faces.view.facelets.TagException;
35  import javax.faces.view.facelets.TagHandler;
36  
37  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletAttribute;
38  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletTag;
39  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
40  import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
41  
42  /**
43   * @author Leonardo Uribe (latest modification by $Author: struberg $)
44   * @version $Revision: 1194849 $ $Date: 2011-10-29 04:28:45 -0500 (Sat, 29 Oct 2011) $
45   */
46  @JSFFaceletTag(name="composite:attribute")
47  public class AttributeHandler extends TagHandler implements InterfaceDescriptorCreator
48  {
49      
50      //private static final Log log = LogFactory.getLog(AttributeHandler.class);
51      private static final Logger log = Logger.getLogger(AttributeHandler.class.getName());
52      
53      /**
54       * String array defining all standard attributes of this tag.
55       * ATTENTION: this array MUST be sorted alphabetically in order to use binary search!!
56       */
57      private static final String[] STANDARD_ATTRIBUTES_SORTED = new String[]
58      {
59          "default",
60          "displayName",
61          "expert",
62          "hidden",
63          "method-signature",
64          "name",
65          "preferred",
66          "required",
67          "shortDescription",
68          "targetAttributeName",
69          "targets",
70          "type"
71      };
72  
73      @JSFFaceletAttribute(name="name",
74              className="javax.el.ValueExpression",
75              deferredValueType="java.lang.String",
76              required=true)
77      private final TagAttribute _name;
78      
79      @JSFFaceletAttribute(name="targets",
80              className="javax.el.ValueExpression",
81              deferredValueType="java.lang.String")
82      private final TagAttribute _targets;
83      
84      /**
85       * If this property is set and the attribute does not have any
86       * value (null), the value set on this property is returned as default
87       * instead null.
88       */
89      @JSFFaceletAttribute(name="default",
90              className="javax.el.ValueExpression",
91              deferredValueType="java.lang.String")
92      private final TagAttribute _default;
93      
94      /**
95       * Only available if ProjectStage is Development.
96       */
97      @JSFFaceletAttribute(name="displayName",
98              className="javax.el.ValueExpression",
99              deferredValueType="java.lang.String")
100     private final TagAttribute _displayName;
101 
102     /**
103      * Indicate if the attribute is required or not
104      * <p>
105      * Myfaces specific feature: this attribute is checked only if project stage is
106      * not ProjectStage.Production when a composite component is created.</p>
107      */
108     @JSFFaceletAttribute(name="required",
109             className="javax.el.ValueExpression",
110             deferredValueType="boolean")
111     private final TagAttribute _required;
112 
113     /**
114      * Only available if ProjectStage is Development.
115      */
116     @JSFFaceletAttribute(name="preferred",
117             className="javax.el.ValueExpression",
118             deferredValueType="boolean")
119     private final TagAttribute _preferred;
120 
121     /**
122      * Only available if ProjectStage is Development.
123      */
124     @JSFFaceletAttribute(name="expert",
125             className="javax.el.ValueExpression",
126             deferredValueType="boolean")
127     private final TagAttribute _expert;
128 
129     /**
130      * Only available if ProjectStage is Development.
131      */
132     @JSFFaceletAttribute(name="shortDescription",
133             className="javax.el.ValueExpression",
134             deferredValueType="java.lang.String")
135     private final TagAttribute _shortDescription;
136 
137     @JSFFaceletAttribute(name="method-signature",
138             className="javax.el.ValueExpression",
139             deferredValueType="java.lang.String")
140     private final TagAttribute _methodSignature;
141 
142     @JSFFaceletAttribute(name="type",
143             className="javax.el.ValueExpression",
144             deferredValueType="java.lang.String")
145     private final TagAttribute _type;
146     
147     /**
148      * The "hidden" flag is used to identify features that are intended only 
149      * for tool use, and which should not be exposed to humans.
150      * Only available if ProjectStage is Development.
151      */
152     @JSFFaceletAttribute(name="hidden",
153             className="javax.el.ValueExpression",
154             deferredValueType="boolean")
155     protected final TagAttribute _hidden;
156     
157     @JSFFaceletAttribute(name="targetAttributeName",
158             className="javax.el.ValueExpression",
159             deferredValueType="java.lang.String")
160     private final TagAttribute _targetAttributeName;
161     
162     /**
163      * Check if the PropertyDescriptor instance created by this handler
164      * can be cacheable or not. 
165      */
166     private boolean _cacheable;
167     
168     /**
169      * Cached instance used by this component. Note here we have a 
170      * "racy single-check". If this field is used, it is supposed 
171      * the object cached by this handler is immutable, and this is
172      * granted if all properties not saved as ValueExpression are
173      * "literal". 
174      */
175     private PropertyDescriptor _propertyDescriptor; 
176     
177     public AttributeHandler(TagConfig config)
178     {
179         super(config);
180         _name = getRequiredAttribute("name");
181         _targets = getAttribute("targets");
182         _default = getAttribute("default");
183         _displayName = getAttribute("displayName");
184         _required = getAttribute("required");
185         _preferred = getAttribute("preferred");
186         _expert = getAttribute("expert");
187         _shortDescription = getAttribute("shortDescription");
188         _methodSignature = getAttribute("method-signature");
189         _type = getAttribute("type");
190         _hidden = getAttribute("hidden");
191         _targetAttributeName = getAttribute("targetAttributeName");
192         
193         // We can reuse the same PropertyDescriptor only if the properties
194         // that requires to be evaluated when apply (build view time)
195         // occur are literal or null. Otherwise we need to create it.
196         // Note that only if ProjectStage is Development, The "displayName",
197         // "shortDescription", "expert", "hidden", and "preferred" attributes are exposed
198         final boolean development = FacesContext.getCurrentInstance()
199                 .isProjectStage(ProjectStage.Development);
200         
201         if (_name.isLiteral() 
202                 && (!development || _areDevelopmentAttributesLiteral()))
203         {
204             // Unfortunately its not possible to create the required 
205             // PropertyDescriptor instance here, because there is no way 
206             // to get a FaceletContext to create ValueExpressions. It is
207             // possible to create it if we not have set all this properties:
208             // targets, default, required, methodSignature, type and possible
209             // unspecified attributes. This prevents the racy single-check.
210             _cacheable = true;
211             if ( _targets == null && _default == null && _required == null &&
212                  _methodSignature == null && _type == null && _targetAttributeName == null &&
213                  !CompositeTagAttributeUtils.containsUnspecifiedAttributes(tag, 
214                          STANDARD_ATTRIBUTES_SORTED))
215             {
216                 _propertyDescriptor = _createPropertyDescriptor(development);
217             }
218         }
219         else
220         {
221             _cacheable = false;
222         }
223     }
224     
225     /**
226      * True if the "displayName", "shortDescription", "expert", "hidden", and
227      * "preferred" attributes are either null or literal.
228      * @return
229      */
230     private boolean _areDevelopmentAttributesLiteral()
231     {
232         return CompositeTagAttributeUtils.areAttributesLiteral(
233                 _displayName, _shortDescription, _expert, _hidden, _preferred);
234     }
235     
236     public void apply(FaceletContext ctx, UIComponent parent)
237             throws IOException
238     {
239         UIComponent compositeBaseParent
240                 = FaceletCompositionContext.getCurrentInstance(ctx).getCompositeComponentFromStack();
241 
242         CompositeComponentBeanInfo beanInfo = 
243             (CompositeComponentBeanInfo) compositeBaseParent.getAttributes()
244             .get(UIComponent.BEANINFO_KEY);
245         
246         if (beanInfo == null)
247         {
248             if (log.isLoggable(Level.SEVERE))
249             {
250                 log.severe("Cannot find composite bean descriptor UIComponent.BEANINFO_KEY ");
251             }
252             return;
253         }
254         
255         List<PropertyDescriptor> attributeList = beanInfo.getPropertyDescriptorsList();
256         
257         if (isCacheable())
258         {
259             if (_propertyDescriptor == null)
260             {
261                 _propertyDescriptor = _createPropertyDescriptor(ctx, compositeBaseParent);
262             }
263             attributeList.add(_propertyDescriptor);
264         }
265         else
266         {
267             PropertyDescriptor attribute = _createPropertyDescriptor(ctx, compositeBaseParent);
268             attributeList.add(attribute);
269         }
270         
271         // Any "next" handler is going to be used to process nested attributes, which we don't want
272         // to do since they can only possibly refer to bean properties.
273         
274         //nextHandler.apply(ctx, parent);
275     }
276     
277     /**
278      * This method could be called only if it is not necessary to set the following properties:
279      * targets, default, required, methodSignature and type
280      * 
281      * @param development true if the current ProjectStage is Development
282      * @return
283      */
284     private PropertyDescriptor _createPropertyDescriptor(boolean development)
285     {
286         try
287         {
288             CompositeComponentPropertyDescriptor attributeDescriptor = 
289                 new CompositeComponentPropertyDescriptor(_name.getValue());
290             
291             // If ProjectStage is Development, The "displayName", "shortDescription",
292             // "expert", "hidden", and "preferred" attributes are exposed
293             if (development)
294             {
295                 CompositeTagAttributeUtils.addDevelopmentAttributesLiteral(attributeDescriptor,
296                         _displayName, _shortDescription, _expert, _hidden, _preferred);
297             }
298             
299             // note that no unspecified attributes are handled here, because the current
300             // tag does not contain any, otherwise this code would not have been called.
301             
302             return attributeDescriptor;
303         }
304         catch (IntrospectionException e)
305         {
306             if (log.isLoggable(Level.SEVERE))
307             {
308                 log.log(Level.SEVERE, "Cannot create PropertyDescriptor for attribute ",e);
309             }
310             throw new TagException(tag,e);
311         }
312     }
313     
314     private PropertyDescriptor _createPropertyDescriptor(FaceletContext ctx, UIComponent parent)
315         throws TagException, IOException
316     {
317         try
318         {
319             CompositeComponentPropertyDescriptor attributeDescriptor = 
320                 new CompositeComponentPropertyDescriptor(_name.getValue(ctx));
321             
322             // The javadoc of ViewDeclarationLanguage.retargetMethodExpressions says that
323             // 'type', 'method-signature', 'targets' should return ValueExpressions.
324             if (_targets != null)
325             {
326                 attributeDescriptor.setValue("targets", _targets.getValueExpression(ctx, String.class));
327             }
328             if (_default != null)
329             {
330                 if (_type != null)
331                 {
332                     String type = _type.getValue(ctx);
333                     Class clazz = String.class;
334                     if (type != null)
335                     {
336                         try
337                         {
338                             clazz = FaceletViewDeclarationLanguage._javaTypeToClass(type);
339                         }
340                         catch (ClassNotFoundException e)
341                         {
342                             //Assume String
343                         }
344                     }
345                     
346                     if (_default.isLiteral())
347                     {
348                         //If it is literal, calculate it and store it on a ValueExpression
349                         attributeDescriptor.setValue("default", _default.getObject(ctx, clazz));
350                     }
351                     else
352                     {
353                         attributeDescriptor.setValue("default", _default.getValueExpression(ctx, clazz));
354                     }
355                 }
356                 else
357                 {
358                     attributeDescriptor.setValue("default", _default.getValueExpression(ctx, String.class));
359                 }
360             }
361             if (_required != null)
362             {
363                 attributeDescriptor.setValue("required", _required.getValueExpression(ctx, Boolean.class));
364             }
365             if (_methodSignature != null)
366             {
367                 attributeDescriptor.setValue("method-signature",
368                                              _methodSignature.getValueExpression(ctx, String.class));
369             }
370             if (_type != null)
371             {
372                 attributeDescriptor.setValue("type", _type.getValueExpression(ctx, String.class));
373             }
374             if (_targetAttributeName != null)
375             {
376                 attributeDescriptor.setValue("targetAttributeName",
377                                              _targetAttributeName.getValueExpression(ctx, String.class));
378             }
379             
380             // If ProjectStage is Development, The "displayName", "shortDescription",
381             // "expert", "hidden", and "preferred" attributes are exposed
382             if (ctx.getFacesContext().isProjectStage(ProjectStage.Development))
383             {
384                 CompositeTagAttributeUtils.addDevelopmentAttributes(attributeDescriptor, ctx, 
385                         _displayName, _shortDescription, _expert, _hidden, _preferred);
386             }
387             
388             // Any additional attributes are exposed as attributes accessible
389             // from the getValue() and attributeNames() methods on FeatureDescriptor
390             CompositeTagAttributeUtils.addUnspecifiedAttributes(attributeDescriptor, tag, 
391                     STANDARD_ATTRIBUTES_SORTED, ctx);
392             
393             return attributeDescriptor;
394         }
395         catch (IntrospectionException e)
396         {
397             if (log.isLoggable(Level.SEVERE))
398             {
399                 log.log(Level.SEVERE, "Cannot create PropertyDescriptor for attribute ",e);
400             }
401             throw new TagException(tag,e);
402         }
403     }
404 
405     public boolean isCacheable()
406     {
407         return _cacheable;
408     }
409     
410     public void setCacheable(boolean cacheable)
411     {
412         _cacheable = cacheable;
413     }
414 }