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.BeanDescriptor;
22  import java.beans.BeanInfo;
23  import java.beans.PropertyDescriptor;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  
32  import javax.el.ELException;
33  import javax.el.ValueExpression;
34  import javax.el.VariableMapper;
35  import javax.faces.FacesException;
36  import javax.faces.application.ProjectStage;
37  import javax.faces.application.Resource;
38  import javax.faces.component.ActionSource;
39  import javax.faces.component.EditableValueHolder;
40  import javax.faces.component.UIComponent;
41  import javax.faces.component.UIPanel;
42  import javax.faces.component.UniqueIdVendor;
43  import javax.faces.component.ValueHolder;
44  import javax.faces.context.FacesContext;
45  import javax.faces.view.AttachedObjectHandler;
46  import javax.faces.view.ViewDeclarationLanguage;
47  import javax.faces.view.facelets.ComponentConfig;
48  import javax.faces.view.facelets.ComponentHandler;
49  import javax.faces.view.facelets.FaceletContext;
50  import javax.faces.view.facelets.FaceletException;
51  import javax.faces.view.facelets.FaceletHandler;
52  import javax.faces.view.facelets.MetaRuleset;
53  import javax.faces.view.facelets.Metadata;
54  import javax.faces.view.facelets.TagException;
55  import javax.faces.view.facelets.TextHandler;
56  
57  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
58  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
59  import org.apache.myfaces.view.facelets.TemplateClient;
60  import org.apache.myfaces.view.facelets.TemplateContext;
61  import org.apache.myfaces.view.facelets.el.VariableMapperWrapper;
62  import org.apache.myfaces.view.facelets.tag.ComponentContainerHandler;
63  import org.apache.myfaces.view.facelets.tag.TagHandlerUtils;
64  import org.apache.myfaces.view.facelets.tag.jsf.ActionSourceRule;
65  import org.apache.myfaces.view.facelets.tag.jsf.ComponentBuilderHandler;
66  import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport;
67  import org.apache.myfaces.view.facelets.tag.jsf.EditableValueHolderRule;
68  import org.apache.myfaces.view.facelets.tag.jsf.ValueHolderRule;
69  import org.apache.myfaces.view.facelets.tag.jsf.core.AjaxHandler;
70  
71  /**
72   * This handler is responsible for apply composite components. It
73   * is created by CompositeResourceLibrary class when a composite component
74   * is found.
75   * 
76   * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
77   * @version $Revision: 1523352 $ $Date: 2013-09-14 18:28:22 -0500 (Sat, 14 Sep 2013) $
78   */
79  public class CompositeComponentResourceTagHandler extends ComponentHandler
80      implements ComponentBuilderHandler, TemplateClient
81  {
82      private final Resource _resource;
83      
84      private Metadata _mapper;
85      
86      private Class<?> _lastType = Object.class;
87      
88      protected volatile Map<String, FaceletHandler> _facetHandlersMap;
89      
90      protected final Collection<FaceletHandler> _componentHandlers;
91      
92      protected final Collection<FaceletHandler> _facetHandlers;
93      
94      public CompositeComponentResourceTagHandler(ComponentConfig config, Resource resource)
95      {
96          super(config);
97          _resource = resource;
98          _facetHandlers = TagHandlerUtils.findNextByType(nextHandler, javax.faces.view.facelets.FacetHandler.class,
99                                                          InsertFacetHandler.class);
100         _componentHandlers = TagHandlerUtils.findNextByType(nextHandler,
101                 javax.faces.view.facelets.ComponentHandler.class,
102                 ComponentContainerHandler.class, TextHandler.class);
103     }
104 
105     public UIComponent createComponent(FaceletContext ctx)
106     {
107         FacesContext facesContext = ctx.getFacesContext();
108         UIComponent component = facesContext.getApplication().createComponent(facesContext, _resource);
109         
110         // Check required attributes if the app is not on production stage. 
111         // Unfortunately, we can't check it on constructor because we need to call
112         // ViewDeclarationLanguage.getComponentMetadata() and on that point it is possible to not
113         // have a viewId.
114         if (!facesContext.isProjectStage(ProjectStage.Production))
115         {
116             BeanInfo beanInfo = (BeanInfo) component.getAttributes().get(UIComponent.BEANINFO_KEY);
117             for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors())
118             {
119                 ValueExpression ve = (ValueExpression) propertyDescriptor.getValue("required");
120                 if (ve != null)
121                 {
122                     Object value = ve.getValue (facesContext.getELContext());
123                     Boolean required = null;
124                     if (value instanceof Boolean)
125                     {
126                         required = (Boolean) value;
127                     }
128                     else
129                     {
130                         required = Boolean.valueOf(value.toString());
131                     } 
132                     
133                     if (required != null && required.booleanValue())
134                     {
135                         Object attrValue = this.tag.getAttributes().get (propertyDescriptor.getName());
136                         
137                         if (attrValue == null)
138                         {
139                             throw new TagException(this.tag, "Attribute '" + propertyDescriptor.getName()
140                                                              + "' is required");
141                         }
142                     }
143                 }
144             }
145         }
146         return component;
147     }
148 
149     @SuppressWarnings("unchecked")
150     @Override
151     public void applyNextHandler(FaceletContext ctx, UIComponent c)
152             throws IOException
153     {
154         //super.applyNextHandler(ctx, c);
155         
156         applyNextHandlerIfNotApplied(ctx, c);
157         
158         applyCompositeComponentFacelet(ctx,c);
159         
160         if (ComponentHandler.isNew(c))
161         {
162             FacesContext facesContext = ctx.getFacesContext();
163             
164             ViewDeclarationLanguage vdl = facesContext.getApplication().getViewHandler().
165                 getViewDeclarationLanguage(facesContext, facesContext.getViewRoot().getViewId());
166 
167             FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx);
168             List<AttachedObjectHandler> handlers = mctx.getAttachedObjectHandlers(c);
169             
170             if (handlers != null)
171             {
172                 vdl.retargetAttachedObjects(facesContext, c, handlers);
173                 
174                 // remove the list of handlers, as it is no longer necessary
175                 mctx.removeAttachedObjectHandlers(c);
176             }
177             
178             vdl.retargetMethodExpressions(facesContext, c);
179             
180             if ( FaceletCompositionContext.getCurrentInstance(ctx).isMarkInitialState())
181             {
182                 // Call it only if we are using partial state saving
183                 c.markInitialState();
184                 // Call it to other components created not bound by a tag handler
185                 c.getFacet(UIComponent.COMPOSITE_FACET_NAME).markInitialState();
186             }            
187         }
188     }
189     
190     @SuppressWarnings("unchecked")
191     protected void applyNextHandlerIfNotApplied(FaceletContext ctx, UIComponent c)
192         throws IOException
193     {
194         //Apply all facelets not applied yet.
195         
196         CompositeComponentBeanInfo beanInfo = 
197             (CompositeComponentBeanInfo) c.getAttributes().get(UIComponent.BEANINFO_KEY);
198         
199         BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
200 
201         boolean insertChildrenUsed = (beanDescriptor.getValue(InsertChildrenHandler.INSERT_CHILDREN_USED) != null);
202         
203         List<String> insertFacetList = (List<String>) beanDescriptor.getValue(InsertFacetHandler.INSERT_FACET_USED);
204         
205         if (nextHandler instanceof javax.faces.view.facelets.CompositeFaceletHandler)
206         {
207             for (FaceletHandler handler :
208                     ((javax.faces.view.facelets.CompositeFaceletHandler)nextHandler).getHandlers())
209             {
210                 if (handler instanceof javax.faces.view.facelets.FacetHandler)
211                 {
212                     if (insertFacetList == null ||
213                         !insertFacetList.contains(((javax.faces.view.facelets.FacetHandler)handler).getFacetName(ctx)))
214                     {
215                         handler.apply(ctx, c);
216                     }
217                 }
218                 else if (handler instanceof InsertFacetHandler)
219                 {
220                     if (insertFacetList == null ||
221                         !insertFacetList.contains( ((InsertFacetHandler)handler).getFacetName(ctx)))
222                     {
223                         handler.apply(ctx, c);
224                     }
225                 }
226                 else if (insertChildrenUsed)
227                 {
228                     if (!(handler instanceof javax.faces.view.facelets.ComponentHandler ||
229                             handler instanceof ComponentContainerHandler ||
230                             handler instanceof TextHandler))
231                     {
232                         handler.apply(ctx, c);
233                     }
234                 }
235                 else
236                 {
237                     handler.apply(ctx, c);
238                 }
239             }
240         }
241         else
242         {
243             if (nextHandler instanceof javax.faces.view.facelets.FacetHandler)
244             {
245                 if (insertFacetList == null ||
246                     !insertFacetList.contains(((javax.faces.view.facelets.FacetHandler)nextHandler).getFacetName(ctx)))
247                 {
248                     nextHandler.apply(ctx, c);
249                 }
250             }
251             else if (nextHandler instanceof InsertFacetHandler)
252             {
253                 if (insertFacetList == null ||
254                     !insertFacetList.contains( ((InsertFacetHandler)nextHandler).getFacetName(ctx)) )
255                 {
256                     nextHandler.apply(ctx, c);
257                 }
258             }
259             else if (insertChildrenUsed)
260             {
261                 if (!(nextHandler instanceof javax.faces.view.facelets.ComponentHandler ||
262                       nextHandler instanceof ComponentContainerHandler ||
263                       nextHandler instanceof TextHandler))
264                 {
265                     nextHandler.apply(ctx, c);
266                 }
267             }
268             else
269             {
270                 nextHandler.apply(ctx, c);
271             }
272         }
273         
274         //Check for required facets
275         Map<String, PropertyDescriptor> facetPropertyDescriptorMap = (Map<String, PropertyDescriptor>)
276             beanDescriptor.getValue(UIComponent.FACETS_KEY);
277         
278         if (facetPropertyDescriptorMap != null)
279         {
280             List<String> facetsRequiredNotFound = null;
281             for (Map.Entry<String, PropertyDescriptor> entry : facetPropertyDescriptorMap.entrySet())
282             {
283                 ValueExpression requiredExpr = (ValueExpression) entry.getValue().getValue("required");
284                 if (requiredExpr != null)
285                 {
286                     Boolean required = (Boolean) requiredExpr.getValue(ctx.getFacesContext().getELContext());
287                     if (Boolean.TRUE.equals(required))
288                     {
289                         initFacetHandlersMap(ctx);
290                         if (!_facetHandlersMap.containsKey(entry.getKey()))
291                         {
292                             if (facetsRequiredNotFound == null)
293                             {
294                                 facetsRequiredNotFound = new ArrayList(facetPropertyDescriptorMap.size());
295                             }
296                             facetsRequiredNotFound.add(entry.getKey());
297                         }
298                         
299                     }
300                 }
301             }
302             if (facetsRequiredNotFound != null && !facetsRequiredNotFound.isEmpty())
303             {
304                 throw new TagException(getTag(), "The following facets are required by the component: "
305                                                  + facetsRequiredNotFound);
306             }
307         }
308     }
309     
310     protected void applyCompositeComponentFacelet(FaceletContext faceletContext, UIComponent compositeComponentBase) 
311         throws IOException
312     {
313         FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(faceletContext);
314         AbstractFaceletContext actx = (AbstractFaceletContext) faceletContext;
315         UIPanel compositeFacetPanel
316                 = (UIPanel) compositeComponentBase.getFacets().get(UIComponent.COMPOSITE_FACET_NAME);
317         if (compositeFacetPanel == null)
318         {
319             compositeFacetPanel = (UIPanel)
320                 faceletContext.getFacesContext().getApplication().createComponent(
321                     faceletContext.getFacesContext(), UIPanel.COMPONENT_TYPE, null);
322             compositeFacetPanel.getAttributes().put(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER,
323                     Boolean.TRUE);
324             compositeComponentBase.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, compositeFacetPanel);
325             
326             // Set an id to the created facet component, to prevent id generation and make
327             // partial state saving work without problem.
328             UniqueIdVendor uniqueIdVendor = mctx.getUniqueIdVendorFromStack();
329             if (uniqueIdVendor == null)
330             {
331                 uniqueIdVendor = ComponentSupport.getViewRoot(faceletContext, compositeComponentBase);
332             }
333             if (uniqueIdVendor != null)
334             {
335                 // UIViewRoot implements UniqueIdVendor, so there is no need to cast to UIViewRoot
336                 // and call createUniqueId()
337                 String uid = uniqueIdVendor.createUniqueId(faceletContext.getFacesContext(),
338                         mctx.getSharedStringBuilder()
339                         .append(compositeComponentBase.getId())
340                         .append("__f_")
341                         .append("cc_facet").toString());
342                 compositeFacetPanel.setId(uid);
343             }            
344         }
345         
346         // Before call applyCompositeComponent we need to add ajax behaviors
347         // to the current compositeComponentBase. Note that super.applyNextHandler()
348         // has already been called, but this point is before vdl.retargetAttachedObjects,
349         // so we can't but this on ComponentTagHandlerDelegate, if we want this to be
350         // applied correctly.
351         Iterator<AjaxHandler> it = ((AbstractFaceletContext) faceletContext).getAjaxHandlers();
352         if (it != null)
353         {
354             while(it.hasNext())
355             {
356                 mctx.addAttachedObjectHandler(
357                         compositeComponentBase, it.next());
358             }
359         }    
360         
361         VariableMapper orig = faceletContext.getVariableMapper();
362         try
363         {
364             faceletContext.setVariableMapper(new VariableMapperWrapper(orig));
365             actx.pushCompositeComponentClient(this);
366             Resource resourceForCurrentView = faceletContext.getFacesContext().getApplication().
367                 getResourceHandler().createResource(_resource.getResourceName(), _resource.getLibraryName());
368             if (resourceForCurrentView != null)
369             {
370                 //Wrap it for serialization.
371                 resourceForCurrentView = new CompositeResouceWrapper(resourceForCurrentView);
372             }
373             else
374             {
375                 //If a resource cannot be resolved it means a default for the current 
376                 //composite component does not exists.
377                 throw new TagException(getTag(), "Composite Component " + getTag().getQName() 
378                         + " requires a default instance that can be found by the installed ResourceHandler.");
379             }
380             actx.applyCompositeComponent(compositeFacetPanel, resourceForCurrentView);
381         }
382         finally
383         {
384             actx.popCompositeComponentClient();
385             faceletContext.setVariableMapper(orig);
386         }
387     }
388 
389     @Override
390     public void setAttributes(FaceletContext ctx, Object instance)
391     {
392         if (instance != null)
393         {
394             UIComponent component = (UIComponent) instance;
395 
396             Class<?> type = instance.getClass();
397             if (_mapper == null || !_lastType.equals(type))
398             {
399                 _lastType = type;
400                 BeanInfo beanInfo = (BeanInfo)component.getAttributes().get(UIComponent.BEANINFO_KEY);
401                 _mapper = createMetaRuleset(type , beanInfo).finish();
402             }
403             
404             _mapper.applyMetadata(ctx, instance);
405         }        
406     }
407 
408     protected MetaRuleset createMetaRuleset(Class<?> type, BeanInfo beanInfo)
409     {
410         MetaRuleset m = new CompositeMetaRulesetImpl(this.getTag(), type, beanInfo);
411         // ignore standard component attributes
412         m.ignore("binding").ignore("id");
413 
414         // add auto wiring for attributes
415         m.addRule(CompositeComponentRule.Instance);
416         
417         // add retarget method expression rules
418         m.addRule(RetargetMethodExpressionRule.Instance);
419         
420         if (ActionSource.class.isAssignableFrom(type))
421         {
422             m.addRule(ActionSourceRule.Instance);
423         }
424 
425         if (ValueHolder.class.isAssignableFrom(type))
426         {
427             m.addRule(ValueHolderRule.Instance);
428 
429             if (EditableValueHolder.class.isAssignableFrom(type))
430             {
431                 m.ignore("submittedValue");
432                 m.ignore("valid");
433                 m.addRule(EditableValueHolderRule.Instance);
434             }
435         }
436         
437         return m;
438     }
439     
440     private void initFacetHandlersMap(FaceletContext ctx)
441     {
442         if (_facetHandlersMap == null)
443         {
444             Map<String, FaceletHandler> map = new HashMap<String, FaceletHandler>();
445             
446             for (FaceletHandler handler : _facetHandlers)
447             {
448                 if (handler instanceof javax.faces.view.facelets.FacetHandler )
449                 {
450                     map.put( ((javax.faces.view.facelets.FacetHandler)handler).getFacetName(ctx), handler);
451                 }
452                 else if (handler instanceof InsertFacetHandler)
453                 {
454                     map.put( ((InsertFacetHandler)handler).getFacetName(ctx), handler);
455                 }
456             }
457             _facetHandlersMap = map;
458         }
459     }
460     
461     public boolean apply(FaceletContext ctx, UIComponent parent, String name)
462             throws IOException, FacesException, FaceletException, ELException
463     {        
464         if (name != null)
465         {
466             //1. Initialize map used to retrieve facets
467             if (_facetHandlers == null || _facetHandlers.isEmpty())
468             {
469                 checkFacetRequired(ctx, parent, name);
470                 return true;
471             }
472 
473             initFacetHandlersMap(ctx);
474 
475             FaceletHandler handler = _facetHandlersMap.get(name);
476 
477             if (handler != null)
478             {
479                 AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
480                 // Pop the current composite component on stack, so #{cc} references
481                 // can be resolved correctly, because they are relative to the page 
482                 // that define it.
483                 FaceletCompositionContext fcc = actx.getFaceletCompositionContext(); 
484                 UIComponent innerCompositeComponent = fcc.getCompositeComponentFromStack(); 
485                 fcc.popCompositeComponentToStack();
486                 // Pop the template context, so ui:xx tags and nested composite component
487                 // cases could work correctly 
488                 TemplateContext itc = actx.popTemplateContext();
489                 try
490                 {
491                     handler.apply(ctx, parent);
492                 }
493                 finally
494                 {
495                     actx.pushTemplateContext(itc);
496                     fcc.pushCompositeComponentToStack(innerCompositeComponent);
497                 }
498                 return true;
499                 
500             }
501             else
502             {
503                 checkFacetRequired(ctx, parent, name);
504                 return true;
505             }
506         }
507         else
508         {
509             AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
510             // Pop the current composite component on stack, so #{cc} references
511             // can be resolved correctly, because they are relative to the page 
512             // that define it.
513             FaceletCompositionContext fcc = actx.getFaceletCompositionContext(); 
514             UIComponent innerCompositeComponent = fcc.getCompositeComponentFromStack(); 
515             fcc.popCompositeComponentToStack();
516             // Pop the template context, so ui:xx tags and nested composite component
517             // cases could work correctly 
518             TemplateContext itc = actx.popTemplateContext();
519             try
520             {
521                 for (FaceletHandler handler : _componentHandlers)
522                 {
523                     handler.apply(ctx, parent);
524                 }
525             }
526             finally
527             {
528                 actx.pushTemplateContext(itc);
529                 fcc.pushCompositeComponentToStack(innerCompositeComponent);
530             }
531             return true;
532         }
533     }
534     
535     private void checkFacetRequired(FaceletContext ctx, UIComponent parent, String name)
536     {
537         AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
538         FaceletCompositionContext fcc = actx.getFaceletCompositionContext(); 
539         UIComponent innerCompositeComponent = fcc.getCompositeComponentFromStack();
540         
541         CompositeComponentBeanInfo beanInfo = 
542             (CompositeComponentBeanInfo) innerCompositeComponent.getAttributes()
543             .get(UIComponent.BEANINFO_KEY);
544         
545         BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
546         
547         Map<String, PropertyDescriptor> insertFacetPropertyDescriptorMap = (Map<String, PropertyDescriptor>)
548             beanDescriptor.getValue(InsertFacetHandler.INSERT_FACET_KEYS);
549 
550         if (insertFacetPropertyDescriptorMap != null && insertFacetPropertyDescriptorMap.containsKey(name))
551         {
552             ValueExpression requiredExpr
553                     = (ValueExpression) insertFacetPropertyDescriptorMap.get(name).getValue("required");
554             
555             if (requiredExpr != null &&
556                 Boolean.TRUE.equals(requiredExpr.getValue(ctx.getFacesContext().getELContext())))
557             {
558                 //Insert facet associated is required, but it was not applied.
559                 throw new TagException(this.tag, "Cannot find facet with name '"+name+"' in composite component");
560             }
561         }
562     }
563 }