View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.taglibs;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.core.ItemType;
38  import info.magnolia.cms.security.AccessDeniedException;
39  import org.apache.commons.lang.StringUtils;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  import javax.jcr.PathNotFoundException;
44  import javax.jcr.RepositoryException;
45  import javax.servlet.http.HttpServletRequest;
46  import javax.servlet.jsp.PageContext;
47  import javax.servlet.jsp.jstl.core.LoopTagStatus;
48  import java.io.Serializable;
49  import java.util.Collection;
50  import java.util.Collections;
51  import java.util.Iterator;
52  
53  
54  /**
55   * Iterate over contentNode collection. contentNodeIterator is used whenever you want to loop over content, typically paragraphs. A parameter
56          contentNodeCollectionName will contain the name of the contentNode you are looping over.
57          contentNodeCollectionName is created by providing a newBar with the corresponding name. This will result in
58          elements being created within that contentNode, and thus allow you to loop over these.
59  
60   * @jsp.tag name="contentNodeIterator" body-content="JSP"
61   * @jsp.tag-example
62   * <cms:contentNodeIterator contentNodeCollectionName="mainColumnParagraphs">
63   *   <cms:adminOnly>
64   *     <cms:editBar/>
65   *   </cms:adminOnly>
66   *   <cms:includeTemplate/>
67   * </cms:contentNodeIterator>
68   *
69   * @author Sameer Charles
70   * @author Fabrizio Giustina
71   * @author David Smith
72   * @version $Revision: 32667 $ ($Author: gjoseph $)
73   */
74  public class ContentNodeIterator extends BaseContentTag {
75      private static final Logger log = LoggerFactory.getLogger(ContentNodeIterator.class);
76  
77      /**
78       * @deprecated
79       */
80      public static final String CONTENT_NODE_COLLECTION_NAME = "contentNodeCollectionName"; //$NON-NLS-1$
81  
82      /**
83       * @deprecated use the <code>varStatus</code> tag attribute to get a reference to a
84       * <code>javax.servlet.jsp.jstl.core.LoopTagStatus</code> instance.
85       */
86      protected static final String CURRENT_INDEX = "currentIndex"; //$NON-NLS-1$
87  
88      /**
89       * @deprecated use the <code>varStatus</code> tag attribute to get a reference to a
90       * <code>javax.servlet.jsp.jstl.core.LoopTagStatus</code> instance.
91       */
92      protected static final String SIZE = "size"; //$NON-NLS-1$
93  
94      /**
95       * Tag attribute.
96       */
97      protected int begin;
98  
99      /**
100      * Tag attribute.
101      */
102     protected Integer end;
103 
104     /**
105      * Tag attribute.
106      */
107     protected int step = 1;
108 
109     /**
110      * Tag attribute.
111      */
112     private String varStatus;
113 
114     /**
115      * Tag attribute.
116      */
117     private Collection items;
118 
119     protected int size;
120 
121     protected int count;
122     protected int index;
123 
124     protected Object current;
125 
126     private Iterator contentNodeIterator;
127 
128     private LoopTagStatus status;
129 
130     /** Size info from outer node iterator, if one exists. */
131     private Integer outerSize = null;
132 
133     /** Current index the outer node iterator is at. */
134     private Integer outerCurrIdx = null;
135 
136     /** Collection iterated over by the outer node iterator. */
137     private String outerCollName = null;
138 
139     /** Resource.getLocalContentNodeCollectionName() value set by outer node iterator. */
140     private String outerResCollName = null;
141 
142     /** Resource.getLocalContentNode set by the outer node iterator. */
143     private Content outerLocalContentNode = null;
144 
145     /** Flag indicating the previous node state should be restored. */
146     private boolean restorePreviousState = false;
147 
148     /**
149      * Zero-based index of first item to process, inclusive.
150      * @jsp.attribute required="false" rtexprvalue="true" type="int"
151      */
152     public void setBegin(int index) {
153         this.begin = index;
154     }
155 
156     /**
157      * Zero-based index of last item to process, inclusive.
158      * @jsp.attribute required="false" rtexprvalue="true" type="int"
159      */
160     public void setEnd(Integer index) {
161         this.end = index;
162     }
163 
164     /**
165      * Process every stepth element (e.g 2 = every second element).
166      * @jsp.attribute required="false" rtexprvalue="true" type="int"
167      */
168     public void setStep(int step) {
169         this.step = step;
170     }
171 
172     /**
173      * Name of variable to hold the loop status with the following properties: index: position of the current item;
174      *  count: number of times through the loop (starting with 1); first: boolean indicator if this is the first
175      *  iteration; last: boolean indicator if this is the last iteration.
176      *
177      * @jsp.attribute required="false" rtexprvalue="true"
178      */
179     public void setVarStatus(String varStatus) {
180         this.varStatus = varStatus;
181     }
182 
183     /**
184      * if this attribute is set, the tag will iterate on the collection directly passed here instead of fetching the
185      * collection named by contentNodeCollectionName. This collection must contains info.magnolia.cms.core.Content
186      * items.
187      * @jsp.attribute required="false" rtexprvalue="true" type="java.util.Collection"
188      */
189     public void setItems(Collection items) {
190         this.items = items;
191     }
192 
193     /**
194      * @jsp.attribute description="nodeDataName is not supported in this tag !" required="false" rtexprvalue="false"
195      */
196     public void setNodeDataName(String name) {
197         throw new UnsupportedOperationException("nodeDataName not supported in this tag");
198     }
199 
200     /**
201      * TODO : provide relevant doc for this attribute -- only overriden to do so.
202      */
203     public void setContentNodeName(String name) {
204         super.setContentNodeName(name);
205     }
206 
207     private Collection getCollection() throws RepositoryException {
208         if (this.items != null) {
209             return this.items;
210         }
211         // If this is a nested iterator, the collection should be from the local content node.
212         Content page = super.getFirstMatchingNode();
213         return page == null ? Collections.EMPTY_LIST : page.getChildren(ItemType.CONTENTNODE);
214     }
215 
216     protected LoopTagStatus getLoopStatus() {
217 
218         class Status implements LoopTagStatus, Serializable {
219 
220             public Object getCurrent() {
221                 return current;
222             }
223 
224             public int getIndex() {
225                 return count + begin - 1;
226             }
227 
228             public int getCount() {
229                 return count;
230             }
231 
232             public boolean isFirst() {
233                 return (count == 1); // count starts with 1
234             }
235 
236             public boolean isLast() {
237                 int lastItem = end != null ? end.intValue() : size;
238                 return count + begin == lastItem;
239             }
240 
241             public Integer getBegin() {
242                 return new Integer(begin);
243             }
244 
245             public Integer getEnd() {
246                 return end;
247             }
248 
249             public Integer getStep() {
250                 return new Integer(step);
251             }
252         }
253 
254         if (status == null) {
255             status = new Status();
256         }
257 
258         return status;
259     }
260 
261     public int doStartTag() {
262         index += begin;
263 
264         Collection children;
265         try {
266             children = getCollection();
267         }
268         catch (PathNotFoundException e) {
269             // ok, this is normal
270             return SKIP_BODY;
271         }
272         catch (AccessDeniedException e) {
273             log.debug(e.getMessage());
274             return SKIP_BODY;
275         }
276         catch (RepositoryException e) {
277             log.error(e.getMessage(), e);
278             return SKIP_BODY;
279         }
280 
281         this.size = children.size();
282 
283         this.savePrevState();
284 
285         pageContext.setAttribute(ContentNodeIterator.SIZE, new Integer(size), PageContext.REQUEST_SCOPE);
286 
287         pageContext.setAttribute(ContentNodeIterator.CURRENT_INDEX, new Integer(this.count), PageContext.REQUEST_SCOPE);
288 
289         pageContext.setAttribute(ContentNodeIterator.CONTENT_NODE_COLLECTION_NAME, getContentNodeCollectionName(), PageContext.REQUEST_SCOPE);
290 
291         Resource.setLocalContentNodeCollectionName(getContentNodeCollectionName());
292 
293         this.contentNodeIterator = children.iterator();
294         for (int i = 0; i < begin; i++) {
295             if (this.contentNodeIterator.hasNext()) {
296                 this.contentNodeIterator.next();
297             }
298         }
299 
300         return doIteration() ? EVAL_BODY_INCLUDE : SKIP_BODY;
301     }
302 
303     public int doAfterBody() {
304         return doIteration() ? EVAL_BODY_AGAIN : SKIP_BODY;
305     }
306 
307     private boolean doIteration() {
308         if (this.contentNodeIterator.hasNext()) {
309 
310             if (this.end != null && this.count > this.end.intValue()) {
311                 return false;
312             }
313 
314             pageContext.setAttribute(
315                 ContentNodeIterator.CURRENT_INDEX,
316                 new Integer(this.count),
317                 PageContext.REQUEST_SCOPE);
318 
319             if (StringUtils.isNotEmpty(varStatus)) {
320                 pageContext.setAttribute(varStatus, getLoopStatus());
321             }
322 
323             for (int j = 0; j < this.step; j++) {
324                 current = this.contentNodeIterator.next();
325                 Resource.setLocalContentNode((Content) current);
326             }
327 
328             this.count++;
329 
330             return true;
331         }
332         return false;
333     }
334 
335     public int doEndTag() {
336 
337         this.restorePrevState();
338         this.restorePreviousState = false;
339         this.size = 0;
340         this.count = 0;
341         this.current = null;
342 
343         if (varStatus != null) {
344             pageContext.removeAttribute(varStatus, PageContext.PAGE_SCOPE);
345         }
346 
347         reset();
348 
349         return EVAL_PAGE;
350     }
351 
352     public void reset() {
353         setContentNodeCollectionName(null); // TODO : is this correct - here ?
354         this.contentNodeIterator = null;
355         this.begin = 0;
356         this.end = null;
357         this.step = 1;
358         this.size = 0;
359         this.count = 0;
360         this.varStatus = null;
361         this.status = null;
362         this.items = null;
363 
364         this.outerCollName = null;
365         this.outerCurrIdx = null;
366         this.outerLocalContentNode = null;
367         this.outerResCollName = null;
368         this.outerSize = null;
369     }
370 
371     /** Checks if a content node iterator tag is already in operation and saves it's state. */
372     private void savePrevState() {
373         HttpServletRequest req = (HttpServletRequest) this.pageContext.getRequest();
374 
375         // savePrevState() was invoked. Enable restorePrevState()
376         this.restorePreviousState = true;
377 
378         if (req.getAttribute(ContentNodeIterator.SIZE) != null) {
379             this.outerSize = (Integer) pageContext.getAttribute(ContentNodeIterator.SIZE, PageContext.REQUEST_SCOPE);
380             this.outerCollName = (String) pageContext.getAttribute(
381                 ContentNodeIterator.CONTENT_NODE_COLLECTION_NAME,
382                 PageContext.REQUEST_SCOPE);
383             this.outerCurrIdx = (Integer) pageContext.getAttribute(
384                 ContentNodeIterator.CURRENT_INDEX,
385                 PageContext.REQUEST_SCOPE);
386             this.outerLocalContentNode = Resource.getLocalContentNode();
387             this.outerResCollName = Resource.getLocalContentNodeCollectionName();
388         }
389         else{
390             this.outerLocalContentNode = Resource.getLocalContentNode();
391         }
392 
393     }
394 
395     private void restorePrevState() {
396         if (this.restorePreviousState) {
397             HttpServletRequest req = (HttpServletRequest) this.pageContext.getRequest();
398             if (this.outerSize != null) {
399                 pageContext.setAttribute(ContentNodeIterator.SIZE, this.outerSize);
400                 pageContext.setAttribute(ContentNodeIterator.CURRENT_INDEX, this.outerCurrIdx);
401                 pageContext.setAttribute(ContentNodeIterator.CONTENT_NODE_COLLECTION_NAME, this.outerCollName);
402                 Resource.setLocalContentNode(this.outerLocalContentNode);
403                 Resource.setLocalContentNodeCollectionName(this.outerResCollName);
404             }
405             else {
406                 if(outerLocalContentNode != null){
407                     Resource.setLocalContentNode(this.outerLocalContentNode);
408                 }
409                 else{
410                     Resource.removeLocalContentNode();
411                 }
412                 Resource.removeLocalContentNodeCollectionName();
413                 pageContext.removeAttribute(ContentNodeIterator.CURRENT_INDEX);
414                 pageContext.removeAttribute(ContentNodeIterator.SIZE);
415                 pageContext.removeAttribute(ContentNodeIterator.CONTENT_NODE_COLLECTION_NAME);
416             }
417         }
418     }
419 
420 }