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