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