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.jstl.core;
20  
21  import java.io.IOException;
22  import java.io.Serializable;
23  import java.lang.reflect.Array;
24  import java.util.Collection;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import javax.el.ELException;
30  import javax.el.ValueExpression;
31  import javax.faces.FacesException;
32  import javax.faces.application.StateManager;
33  import javax.faces.component.UIComponent;
34  import javax.faces.event.PhaseId;
35  import javax.faces.view.facelets.FaceletContext;
36  import javax.faces.view.facelets.FaceletException;
37  import javax.faces.view.facelets.TagAttribute;
38  import javax.faces.view.facelets.TagAttributeException;
39  import javax.faces.view.facelets.TagConfig;
40  import javax.faces.view.facelets.TagHandler;
41  
42  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletAttribute;
43  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletTag;
44  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
45  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
46  import org.apache.myfaces.view.facelets.PageContext;
47  import org.apache.myfaces.view.facelets.el.FaceletStateValueExpression;
48  import org.apache.myfaces.view.facelets.tag.ComponentContainerHandler;
49  import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport;
50  import org.apache.myfaces.view.facelets.tag.jsf.FaceletState;
51  
52  /**
53   * The basic iteration tag, accepting many different
54   * collection types and supporting subsetting and other
55   * functionality
56   * 
57   * @author Jacob Hookom
58   * @author Andrew Robinson
59   * @version $Id$
60   */
61  @JSFFaceletTag(name="c:forEach")
62  public final class ForEachHandler extends TagHandler implements ComponentContainerHandler
63  {
64  
65      private static class ArrayIterator implements Iterator<Object>
66      {
67  
68          protected final Object array;
69  
70          protected int i;
71  
72          protected final int len;
73  
74          public ArrayIterator(Object src)
75          {
76              this.i = 0;
77              this.array = src;
78              this.len = Array.getLength(src);
79          }
80  
81          @Override
82          public boolean hasNext()
83          {
84              return this.i < this.len;
85          }
86  
87          @Override
88          public Object next()
89          {
90              return Array.get(this.array, this.i++);
91          }
92  
93          @Override
94          public void remove()
95          {
96              throw new UnsupportedOperationException();
97          }
98      }
99  
100     /**
101      * If items specified:
102      * Iteration begins at the item located at the
103      * specified index. First item of the collection has
104      * index 0.
105      * If items not specified:
106      * Iteration begins with index set at the value
107      * specified.
108      */
109     @JSFFaceletAttribute(className="int")
110     private final TagAttribute begin;
111 
112     /**
113      * If items specified:
114      * Iteration ends at the item located at the
115      * specified index (inclusive).
116      * If items not specified:
117      * Iteration ends when index reaches the value
118      * specified.
119      */
120     @JSFFaceletAttribute(className="int")
121     private final TagAttribute end;
122 
123     /**
124      * Collection of items to iterate over.
125      */
126     @JSFFaceletAttribute(className="javax.el.ValueExpression")
127     private final TagAttribute items;
128 
129     /**
130      * Iteration will only process every step items of
131      * the collection, starting with the first one.
132      */
133     @JSFFaceletAttribute(className="int")
134     private final TagAttribute step;
135 
136     private final TagAttribute tranzient;
137 
138     /**
139      * Name of the exported scoped variable for the
140      * current item of the iteration. This scoped
141      * variable has nested visibility. Its type depends
142      * on the object of the underlying collection.
143      */
144     @JSFFaceletAttribute(className="java.lang.String")
145     private final TagAttribute var;
146 
147     /**
148      * Name of the exported scoped variable for the
149      * status of the iteration. 
150      */
151     @JSFFaceletAttribute(className="java.lang.String")
152     private final TagAttribute varStatus;
153 
154     /**
155      * @param config
156      */
157     public ForEachHandler(TagConfig config)
158     {
159         super(config);
160         this.items = this.getAttribute("items");
161         this.var = this.getAttribute("var");
162         this.begin = this.getAttribute("begin");
163         this.end = this.getAttribute("end");
164         this.step = this.getAttribute("step");
165         this.varStatus = this.getAttribute("varStatus");
166         this.tranzient = this.getAttribute("transient");
167 
168         if (this.items == null && this.begin != null && this.end == null)
169         {
170             throw new TagAttributeException(this.tag, this.begin,
171                                             "If the 'items' attribute is not specified, but the 'begin' attribute is, "
172                                             + "then the 'end' attribute is required");
173         }
174     }
175 
176     @Override
177     public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException,
178             ELException
179     {
180         int e = this.getEnd(ctx);
181         Object src = null;
182         ValueExpression srcVE = null;
183         if (this.items != null)
184         {
185             srcVE = this.items.getValueExpression(ctx, Object.class);
186             src = srcVE.getValue(ctx);
187         }
188         else
189         {
190             byte[] b = new byte[e + 1];
191             for (int i = 0; i < b.length; i++)
192             {
193                 b[i] = (byte) i;
194             }
195             src = b;
196         }
197         FaceletCompositionContext fcc = FaceletCompositionContext.getCurrentInstance(ctx);
198         AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
199         // Just increment one number to ensure the prefix doesn't conflict later if two
200         // c:forEach are close between each other. Note c:forEach is different from
201         // c:if tag and doesn't require a section because c:forEach requires to provide
202         // multiple sections starting with a specified "base" related to the element
203         // position and value in the collection.
204         fcc.incrementUniqueComponentId();
205         String uniqueId = actx.generateUniqueFaceletTagId(fcc.generateUniqueId(), tagId);
206         if (src != null)
207         {
208             PageContext pctx = actx.getPageContext();
209             // c:forEach is special because it requires FaceletState even if no pss is used.
210             FaceletState restoredFaceletState = ComponentSupport.getFaceletState(ctx, parent, false);
211             IterationState restoredSavedOption = (restoredFaceletState == null) ? null : 
212                 (IterationState) restoredFaceletState.getState(uniqueId);
213 
214             if (restoredSavedOption != null)
215             {            
216                 if (!PhaseId.RESTORE_VIEW.equals(ctx.getFacesContext().getCurrentPhaseId()))
217                 {
218                     // Refresh, evaluate and synchronize state
219                     applyOnRefresh(ctx, fcc, pctx, parent, uniqueId, src, srcVE, restoredSavedOption);
220                 }
221                 else
222                 {
223                     // restore view, don't record iteration, use the saved value
224                     applyOnRestore(ctx, fcc, pctx, parent, uniqueId, src, srcVE, restoredSavedOption);
225                 }
226             }
227             else
228             {
229                 // First time         
230                 applyFirstTime(ctx, fcc, pctx, parent, uniqueId, src, srcVE);
231             }
232         }
233 
234         if (fcc.isUsingPSSOnThisView() && fcc.isRefreshTransientBuildOnPSS() && !fcc.isRefreshingTransientBuild())
235         {
236             //Mark the parent component to be saved and restored fully.
237             ComponentSupport.markComponentToRestoreFully(ctx.getFacesContext(), parent);
238         }
239         if (fcc.isDynamicComponentSection())
240         {
241             ComponentSupport.markComponentToRefreshDynamically(ctx.getFacesContext(), parent);
242         }
243     }
244     
245     private void setVar(FaceletContext ctx, UIComponent parent,
246         String uniqueId, String base, boolean t, Object src, 
247         ValueExpression srcVE, Object value, String v, int i)
248     {
249         // set the var
250         if (v != null)
251         {
252             ValueExpression ve;
253             if (t || srcVE == null)
254             {
255                 if (value == null)
256                 {
257                     ve = null;
258                 }
259                 else
260                 {
261                     ve = ctx.getExpressionFactory().createValueExpression(
262                                 value, Object.class);
263                 }
264             }
265             else
266             {
267                 ve = this.getVarExpr(srcVE, src, value, i);
268             }
269             setVar(ctx, parent, uniqueId, base, v, ve, srcVE);
270         }
271     }
272     
273     private void setVar(FaceletContext ctx, UIComponent parent,
274         String uniqueId, String base, String v, ValueExpression ve, ValueExpression srcVE)
275     {
276         AbstractFaceletContext actx = ((AbstractFaceletContext) ctx);
277         PageContext pctx = actx.getPageContext();
278         //if (ELExpressionCacheMode.alwaysRecompile.equals(actx.getELExpressionCacheMode()))
279         //{
280         if (srcVE != null)
281         {
282             FaceletState faceletState = ComponentSupport.getFaceletState(ctx, parent, true);
283             faceletState.putBinding(uniqueId, base, ve);
284 
285             //Put the indirect EL into context
286             ValueExpression fve = new FaceletStateValueExpression(uniqueId, base);
287             pctx.getAttributes().put(v, fve);
288         }
289         else
290         {
291             pctx.getAttributes().put(v, ve);
292         }
293     }
294     
295     private void applyFirstTime(FaceletContext ctx, FaceletCompositionContext fcc, PageContext pctx, 
296         UIComponent parent, String uniqueId, Object src, ValueExpression srcVE) throws IOException
297     {
298         int s = this.getBegin(ctx);
299         int e = this.getEnd(ctx);
300         int m = this.getStep(ctx);
301         Integer sO = this.begin != null ? Integer.valueOf(s) : null;
302         Integer eO = this.end != null ? Integer.valueOf(e) : null;
303         Integer mO = this.step != null ? Integer.valueOf(m) : null;
304         boolean t = this.getTransient(ctx);
305         IterationState iterationState = new IterationState();
306 
307         boolean serializableValues = true;
308         Iterator<?> itr = this.toIterator(src);
309         if (itr != null)
310         {
311             int i = 0;
312 
313             // move to start
314             while (i < s && itr.hasNext())
315             {
316                 itr.next();
317                 i++;
318             }
319 
320             String v = this.getVarName(ctx);
321             String vs = this.getVarStatusName(ctx);
322             ValueExpression vO = this.capture(v, pctx);
323             ValueExpression vsO = this.capture(vs, pctx);
324             int mi = 0;
325             Object value = null;
326             try
327             {
328                 boolean first = true;
329                 while (i <= e && itr.hasNext())
330                 {
331                     value = itr.next();
332 
333                     // first time, use the counter
334                     Integer count = iterationState.getCounter();
335                     String base = count.toString();
336                     iterationState.setCounter(count+1);
337 
338                     if (value instanceof Serializable)
339                     {
340                         iterationState.getValueList().add(
341                             new Object[]{base, value, i});
342                     }
343                     else
344                     {
345                         serializableValues = false;
346                     }
347 
348                     try
349                     {
350                         fcc.startComponentUniqueIdSection(base);
351 
352                         setVar(ctx, parent, uniqueId, base, t, src, srcVE, value, v, i);
353                         boolean last = !itr.hasNext();
354                         // set the varStatus
355                         if (vs != null)
356                         {
357                             IterationStatus itrS = new IterationStatus(first, last, i, sO, eO, mO, value);
358                             ValueExpression ve;
359                             if (t || srcVE == null)
360                             {
361                                 if (srcVE == null)
362                                 {
363                                     ve = null;
364                                 }
365                                 else
366                                 {
367                                     ve = ctx.getExpressionFactory().createValueExpression(
368                                                 itrS, Object.class);
369                                 }
370                             }
371                             else
372                             {
373                                 ve = new IterationStatusExpression(itrS);
374                             }
375                             setVar(ctx, parent, uniqueId, base+"_vs", vs, ve, srcVE);
376                         }
377 
378                         // execute body
379                         this.nextHandler.apply(ctx, parent);
380                     }
381                     finally
382                     {
383                         fcc.endComponentUniqueIdSection(base);
384                     }
385 
386                     // increment steps
387                     mi = 1;
388                     while (mi < m && itr.hasNext())
389                     {
390                         itr.next();
391                         mi++;
392                         i++;
393                     }
394                     i++;
395 
396                     first = false;
397                 }
398             }
399             finally
400             {
401                 removeVarAndVarStatus(pctx, v, vO, vs, vsO);
402             }
403         }
404         if (serializableValues)
405         {
406             FaceletState faceletState = ComponentSupport.getFaceletState(ctx, parent, true);
407             faceletState.putState(uniqueId, iterationState);
408         }
409     }
410     
411     private void applyOnRestore(FaceletContext ctx, FaceletCompositionContext fcc, PageContext pctx, 
412         UIComponent parent, String uniqueId, Object src, ValueExpression srcVE, IterationState restoredSavedOption)
413         throws IOException
414     {
415         int s = this.getBegin(ctx);
416         int e = this.getEnd(ctx);
417         int m = this.getStep(ctx);
418         Integer sO = this.begin != null ? Integer.valueOf(s) : null;
419         Integer eO = this.end != null ? Integer.valueOf(e) : null;
420         Integer mO = this.step != null ? Integer.valueOf(m) : null;
421         boolean t = this.getTransient(ctx);
422 
423         // restore view, don't record iteration, use the saved value
424         String v = this.getVarName(ctx);
425         String vs = this.getVarStatusName(ctx);
426         ValueExpression vO = this.capture(v, pctx);
427         ValueExpression vsO = this.capture(vs, pctx);
428         Object value = null;
429         try
430         {
431             int size = restoredSavedOption.getValueList().size();
432             for (int si = 0; si < size; si++)
433             {
434                 Object[] stateValue = restoredSavedOption.getValueList().get(si);
435                 value = stateValue[1];
436                 String base = (String) stateValue[0];
437 
438                 try
439                 {
440                     fcc.startComponentUniqueIdSection(base);
441 
442                     setVar(ctx, parent, uniqueId, base, t, src, srcVE, value, v, (Integer) stateValue[2]);
443 
444                     boolean first = (si == 0);
445                     boolean last = (si == size-1);
446                     int i = (Integer)stateValue[2];
447                     // set the varStatus
448                     if (vs != null)
449                     {
450                         IterationStatus itrS = new IterationStatus(first, last, i, sO, eO, mO, value);
451                         ValueExpression ve;
452                         if (t || srcVE == null)
453                         {
454                             if (srcVE == null)
455                             {
456                                 ve = null;
457                             }
458                             else
459                             {
460                                 ve = ctx.getExpressionFactory().createValueExpression(
461                                             itrS, Object.class);
462                             }
463                         }
464                         else
465                         {
466                             ve = new IterationStatusExpression(itrS);
467                         }
468                         setVar(ctx, parent, uniqueId, base+"_vs", vs, ve, srcVE);
469                     }
470 
471                     // execute body
472                     this.nextHandler.apply(ctx, parent);
473                 }
474                 finally
475                 {
476                     fcc.endComponentUniqueIdSection(base);
477                 }
478             }
479         }
480         finally
481         {
482             removeVarAndVarStatus(pctx, v, vO, vs, vsO);
483         }
484     }
485     
486     private void applyOnRefresh(FaceletContext ctx, FaceletCompositionContext fcc, PageContext pctx, 
487         UIComponent parent, String uniqueId, Object src, ValueExpression srcVE, IterationState restoredSavedOption)
488         throws IOException
489     {
490         int s = this.getBegin(ctx);
491         int e = this.getEnd(ctx);
492         int m = this.getStep(ctx);
493         Integer sO = this.begin != null ? Integer.valueOf(s) : null;
494         Integer eO = this.end != null ? Integer.valueOf(e) : null;
495         Integer mO = this.step != null ? Integer.valueOf(m) : null;
496         boolean t = this.getTransient(ctx);
497 
498         // Refresh, evaluate and synchronize state
499         Iterator<?> itr = this.toIterator(src);
500         IterationState iterationState = new IterationState();
501         iterationState.setCounter(restoredSavedOption.getCounter());
502         if (itr != null)
503         {
504             int i = 0;
505 
506             // move to start
507             while (i < s && itr.hasNext())
508             {
509                 itr.next();
510                 i++;
511             }
512 
513             String v = this.getVarName(ctx);
514             String vs = this.getVarStatusName(ctx);
515             ValueExpression vO = this.capture(v, pctx);
516             ValueExpression vsO = this.capture(vs, pctx);
517             int mi = 0;
518             Object value = null;
519             int stateIndex = 0;
520             try
521             {
522                 boolean first = true;
523                 while (i <= e && itr.hasNext())
524                 {
525                     value = itr.next();
526                     Object[] stateValue = null; /*restoredSavedOption.getValueList().get(stateIndex);*/
527                     String base = null;
528                     boolean found = false;
529 
530                     // The important thing here is use the same base for the generated component ids
531                     // for each element in the iteration that was used on the restore. To do that
532                     // 
533                     int stateIndexCheck = stateIndex;
534                     for (; stateIndexCheck < restoredSavedOption.getValueList().size(); stateIndexCheck++)
535                     {
536                         stateValue = restoredSavedOption.getValueList().get(stateIndexCheck);
537                         if (value.equals(stateValue[1]))
538                         {
539                             found = true;
540                             break;
541                         }
542                     }
543                     if (found)
544                     {
545                         stateIndex = stateIndexCheck;
546                         base = (String) stateValue[0];
547                         stateIndex++;
548                     }
549                     else
550                     {
551                         // No state, added item, create new count
552                         Integer count = iterationState.getCounter();
553                         base = count.toString();
554                         iterationState.setCounter(count+1);
555                         stateValue = null;
556                     }
557 
558                     if (value instanceof Serializable)
559                     {
560                         iterationState.getValueList().add(
561                             new Object[]{base, value, i});
562                     }
563 
564                     try
565                     {
566                         fcc.startComponentUniqueIdSection(base);
567 
568                         setVar(ctx, parent, uniqueId, base, t, src, srcVE, value, v, i);
569 
570                         boolean last = !itr.hasNext();
571                         // set the varStatus
572                         if (vs != null)
573                         {
574                             IterationStatus itrS = new IterationStatus(first, last, i, sO, eO, mO, value);
575                             ValueExpression ve;
576                             if (t || srcVE == null)
577                             {
578                                 if (srcVE == null)
579                                 {
580                                     ve = null;
581                                 }
582                                 else
583                                 {
584                                     ve = ctx.getExpressionFactory().createValueExpression(
585                                                 itrS, Object.class);
586                                 }
587                             }
588                             else
589                             {
590                                 ve = new IterationStatusExpression(itrS);
591                             }
592                             setVar(ctx, parent, uniqueId, base+"_vs", vs, ve, srcVE);
593                         }
594                         //setVarStatus(ctx, pctx, t, sO, eO, mO, srcVE, value, vs, first, !itr.hasNext(), i);
595 
596                         // execute body
597                         boolean markInitialState = (stateValue == null);// !restoredSavedOption.equals(i)
598                         boolean oldMarkInitialState = false;
599                         Boolean isBuildingInitialState = null;
600                         try
601                         {
602                             if (markInitialState && fcc.isUsingPSSOnThisView())
603                             {
604                                 //set markInitialState flag
605                                 oldMarkInitialState = fcc.isMarkInitialState();
606                                 fcc.setMarkInitialState(true);
607                                 isBuildingInitialState = (Boolean) ctx.getFacesContext().getAttributes().put(
608                                         StateManager.IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
609                             }                                
610                             this.nextHandler.apply(ctx, parent);
611                         }
612                         finally
613                         {
614                             if (markInitialState && fcc.isUsingPSSOnThisView())
615                             {
616                                 //unset markInitialState flag
617                                 if (isBuildingInitialState == null)
618                                 {
619                                     ctx.getFacesContext().getAttributes().remove(
620                                             StateManager.IS_BUILDING_INITIAL_STATE);
621                                 }
622                                 else
623                                 {
624                                     ctx.getFacesContext().getAttributes().put(
625                                             StateManager.IS_BUILDING_INITIAL_STATE, isBuildingInitialState);
626                                 }
627                                 fcc.setMarkInitialState(oldMarkInitialState);
628                             }
629                         }
630                     }
631                     finally
632                     {
633                         fcc.endComponentUniqueIdSection(base);
634                     }
635 
636                     // increment steps
637                     mi = 1;
638                     while (mi < m && itr.hasNext())
639                     {
640                         itr.next();
641                         mi++;
642                         i++;
643                     }
644                     i++;
645 
646                     first = false;
647                 }
648             }
649             finally
650             {
651                 removeVarAndVarStatus(pctx, v, vO, vs, vsO);
652             }
653         }
654         FaceletState faceletState = ComponentSupport.getFaceletState(ctx, parent, true);
655         faceletState.putState(uniqueId, iterationState);
656     }
657     
658     private void removeVarAndVarStatus(PageContext pctx, String v, ValueExpression vO, String vs, ValueExpression vsO)
659     {
660         //Remove them from PageContext
661         if (v != null)
662         {
663             pctx.getAttributes().put(v, vO);
664         }
665         else
666         {
667             pctx.getAttributes().remove(v);
668         }
669         if (vs != null)
670         {
671             pctx.getAttributes().put(vs, vsO);
672         }
673         else
674         {
675             pctx.getAttributes().remove(vs);
676         }
677     }
678 
679     private ValueExpression capture(String name, PageContext pctx)
680     {
681         if (name != null)
682         {
683             return pctx.getAttributes().put(name, null);
684         }
685         return null;
686     }
687 
688     private int getBegin(FaceletContext ctx)
689     {
690         if (this.begin != null)
691         {
692             return this.begin.getInt(ctx);
693         }
694         return 0;
695     }
696 
697     private int getEnd(FaceletContext ctx)
698     {
699         if (this.end != null)
700         {
701             return this.end.getInt(ctx);
702         }
703         return Integer.MAX_VALUE - 1; // hotspot bug in the JVM
704     }
705 
706     private int getStep(FaceletContext ctx)
707     {
708         if (this.step != null)
709         {
710             return this.step.getInt(ctx);
711         }
712         return 1;
713     }
714 
715     private boolean getTransient(FaceletContext ctx)
716     {
717         if (this.tranzient != null)
718         {
719             return this.tranzient.getBoolean(ctx);
720         }
721         return false;
722     }
723 
724     private ValueExpression getVarExpr(ValueExpression ve, Object src, Object value, int i)
725     {
726         if (src instanceof List || src.getClass().isArray())
727         {
728             //return new IndexedValueExpression(ve, i);
729             return new IteratedValueExpression(ve, value);
730         }
731         else if (src instanceof Map && value instanceof Map.Entry)
732         {
733             return new MappedValueExpression(ve, (Map.Entry) value);
734         }
735         else if (src instanceof Collection)
736         {
737             return new IteratedValueExpression(ve, value);
738         }
739         throw new IllegalStateException("Cannot create VE for: " + src);
740     }
741 
742     private String getVarName(FaceletContext ctx)
743     {
744         if (this.var != null)
745         {
746             return this.var.getValue(ctx);
747         }
748         return null;
749     }
750 
751     private String getVarStatusName(FaceletContext ctx)
752     {
753         if (this.varStatus != null)
754         {
755             return this.varStatus.getValue(ctx);
756         }
757         return null;
758     }
759 
760     private Iterator<?> toIterator(Object src)
761     {
762         if (src == null)
763         {
764             return null;
765         }
766         else if (src instanceof Collection)
767         {
768             return ((Collection<?>) src).iterator();
769         }
770         else if (src instanceof Map)
771         {
772             return ((Map<?, ?>) src).entrySet().iterator();
773         }
774         else if (src.getClass().isArray())
775         {
776             return new ArrayIterator(src);
777         }
778         else
779         {
780             throw new TagAttributeException(this.tag, this.items,
781                     "Must evaluate to a Collection, Map, Array, or null.");
782         }
783     }
784 
785 }