View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 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.beans.config.ContentRepository;
37  import info.magnolia.cms.beans.runtime.FileProperties;
38  import info.magnolia.cms.core.Content;
39  import info.magnolia.cms.core.NodeData;
40  import info.magnolia.cms.i18n.I18nContentSupportFactory;
41  import info.magnolia.cms.util.ContentUtil;
42  import info.magnolia.cms.util.DateUtil;
43  import info.magnolia.link.LinkFactory;
44  import info.magnolia.link.LinkTransformerManager;
45  import info.magnolia.link.LinkUtil;
46  import info.magnolia.link.LinkException;
47  import info.magnolia.context.MgnlContext;
48  import org.apache.commons.lang.StringEscapeUtils;
49  import org.apache.commons.lang.StringUtils;
50  import org.apache.commons.lang.exception.NestableRuntimeException;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  import javax.jcr.PropertyType;
55  import javax.servlet.jsp.JspWriter;
56  import javax.servlet.jsp.PageContext;
57  import java.io.IOException;
58  import java.util.Date;
59  import java.util.Locale;
60  
61  
62  /**
63   * Writes out the content of a nodeData or - for nodeData of type binary - information of the nodeData.
64   * @jsp.tag name="out" body-content="empty"
65   * @jsp.tag-example
66   * <!-- EXAMPLE - outputting a nodes value into the page -->
67   * <!-- output the value stored in the node namd "title" -->
68   *
69   * <cms:out nodeDataName="title"/>
70   *
71   * <!-- EXAMPLE - outputting a node into an EL variable -->
72   * <!-- output the value stored in the node namd "myprop" to a variable named "check" -->
73   * <!-- thus exposing it to EL functionality -->
74   *
75   * <cms:out nodeDataName="myprop" var="check"/>
76   * <c:if test="${check == 'ok'}">
77   * done
78   * </if>
79   *
80   * <!-- EXAMPLE - outputing a link from a uuid stored in a node -->
81   * <!-- output a relative link to the page whose UUID is stored in the node named "link" to a variable "relative_link" -->
82   *
83   * <cms:out nodeDataName="link" var="relative_link uuidToLink="relative" />
84   * <a href="${relative_link}">go to page</a>
85   *
86   * <!-- EXAMPLE - writing a binary file's URL out as a variable -->
87   * <!-- this example shows how to display an image stored in the content repository using cms:out -->
88   *
89   * <cms:ifNotEmpty nodeDataName="image">
90   * <cms:out nodeDataName="image" var="imageurl" />
91   * <img class="navIcon" src="${pageContext.request.contextPath}${imageurl}" />
92   * </cms:ifNotEmpty>
93   *
94   * @author Sameer Charles
95   * @author Fabrizio Giustina
96   * @version $Revision: 41137 $ ($Author: gjoseph $)
97   */
98  public class Out extends BaseContentTag {
99  
100     private static final Logger log = LoggerFactory.getLogger(Out.class);
101 
102     /**
103      * Fake nodeDataName returning the node's name.
104      */
105     public static final String NODE_NAME_NODEDATANAME = "name";
106 
107     /**
108      * Fake nodeDataName returning the node's handle.
109      */
110     private static final String PATH_NODEDATANAME = "path";
111 
112     /**
113      * Fake nodeDataName returning the node's handle.
114      */
115     private static final String HANDLE_NODEDATANAME = "handle";
116 
117     /**
118      * Fake nodeDataName returning the node's uuid.
119      */
120     private static final String UUID_NODEDATANAME = "uuid";
121 
122     /**
123      * No uuid to link resolving.
124      */
125     public static final String LINK_RESOLVING_NONE = "none";
126 
127     /**
128      * Resolve to a absolute link but do not use the repository to uri mapping.
129      */
130     private static final String LINK_RESOLVING_HANDLE = "handle";
131 
132     /**
133      * Resolve to relative path. Path is relative to current page.
134      */
135     public static final String LINK_RESOLVING_RELATIVE = "relative";
136 
137     /**
138      * Resolve to a absolute link using the repository to uri mapping.
139      */
140     public static final String LINK_RESOLVING_ABSOLUTE = "absolute";
141 
142     /**
143      * Stable serialVersionUID.
144      */
145     private static final long serialVersionUID = 222L;
146 
147     private static final String DEFAULT_LINEBREAK = "<br />"; //$NON-NLS-1$
148 
149     private static final String DEFAULT_DATEPATTERN = DateUtil.FORMAT_DATE_MEDIUM; //$NON-NLS-1$
150 
151     private String defaultValue = StringUtils.EMPTY;
152 
153     private String fileProperty = StringUtils.EMPTY;
154 
155     private String datePattern = DEFAULT_DATEPATTERN; // according to ISO 8601
156 
157     private String dateLanguage;
158 
159     private String lineBreak = DEFAULT_LINEBREAK;
160 
161     private boolean escapeXml = false;
162 
163     private String uuidToLink = LINK_RESOLVING_NONE;
164 
165     private String uuidToLinkRepository = ContentRepository.WEBSITE;
166 
167 
168     /**
169      * If set, the result of the evaluation will be set to a variable named from this attribute (and in the scope
170      * defined using the "scope" attribute, defaulting to PAGE) instead of being written directly to the page.
171      */
172     private String var;
173 
174     /**
175      * Scope for the attribute named from the "var" parameter. Setting this attribute doesn't have any effect if "var"
176      * is not set.
177      */
178     private int scope = PageContext.PAGE_SCOPE;
179 
180     /**
181      * The name of the nodeData you wish to write out (required). There are following special values supported
182      * name, uuid, path, handle. If you specify one of special values, the behavior changes to output the name, uuid,
183      * path, or handle of the node instead of the value it stores.
184      * @jsp.attribute required="true" rtexprvalue="true"
185      * TODO ... this is just overriding BaseContentTag.setNodeDataName() to set proper description and attributes ... :(
186      */
187     public void setNodeDataName(String name) {
188         super.setNodeDataName(name);
189     }
190 
191     /**
192      * If set, the result of the evaluation will be set to a variable named from this attribute (and in the scope
193      * defined using the "scope" attribute, defaulting to PAGE) instead of being written directly to the page.
194      * @jsp.attribute required="false" rtexprvalue="true"
195      */
196     public void setVar(String var) {
197         this.var = var;
198     }
199 
200     /**
201      * Determines whether output will be XML escaped.
202      * @jsp.attribute required="false" rtexprvalue="true" type="boolean"
203      */
204     public void setEscapeXml(boolean escapeXml) {
205         this.escapeXml = escapeXml;
206     }
207 
208     /**
209      * Scope for the attribute named from the "var" parameter.
210      * Setting this attribute doesn't have any effect if "var" is not set.
211      * @jsp.attribute required="false" rtexprvalue="true"
212      */
213     public void setScope(String scope) {
214         if ("request".equalsIgnoreCase(scope)) { //$NON-NLS-1$
215             this.scope = PageContext.REQUEST_SCOPE;
216         }
217         else if ("session".equalsIgnoreCase(scope)) { //$NON-NLS-1$
218             this.scope = PageContext.SESSION_SCOPE;
219         }
220         else if ("application".equalsIgnoreCase(scope)) { //$NON-NLS-1$
221             this.scope = PageContext.APPLICATION_SCOPE;
222         }
223         else {
224             // default
225             this.scope = PageContext.PAGE_SCOPE;
226         }
227     }
228 
229     /**
230      * Sets which information of a file to retrieve. Only applies for nodeDatas of type=Binary.
231      * Supported values (sample value):
232      *
233      * <ul>
234      * <li><b>path (default): </b> path inlcuding the filename (/dev/mainColumnParagraphs/0/image/Alien.png)</li>
235      * <li><b>name </b>: name and extension (Alien.png)</li>
236      * <li><b>extension: </b> extension as is (Png)</li>
237      * <li><b>extensionLowerCase: </b> extension lower case (png)</li>
238      * <li><b>extensionUpperCase: </b> extension upper case (PNG)</li>
239      * <li><b>nameWithoutExtension: </b> (Alien)</li>
240      * <li><b>handle: </b> /dev/mainColumnParagraphs/0/image</li>
241      * <li><b>icon: </b>the default icon for the type of document</li>
242      * <li><b>pathWithoutName: </b> (/dev/mainColumnParagraphs/0/image.png)</li>
243      * <li><b>size: </b> size in bytes (2827)</li>
244      * <li><b>sizeString: </b> size in bytes, KB or MB - max. 3 digits before comma - with unit (2.7 KB)</li>
245      * <li><b>contentType: </b> (image/png)</li>
246      * <li><b>width: </b>image width in pixels (images only)</li>
247      * <li><b>height: </b>image height in pixels (images only)</li>
248      * </ul>
249      *
250      * @jsp.attribute required="false" rtexprvalue="true"
251      */
252     public void setFileProperty(String property) {
253         this.fileProperty = property;
254     }
255 
256     /**
257      * Sets the output date format, as per java.text.SimpleDateFormat. Default is "yyyy-MM-dd".
258      * Only applies for nodeDatas of type=Date.
259      * <ul>
260      * <li><b>G </b> Era designator Text AD
261      * <li><b>y </b> Year Year 1996; 96
262      * <li><b>M </b> Month in year Month July; Jul; 07
263      * <li><b>w </b> Week in year Number 27
264      * <li><b>W </b> Week in month Number 2
265      * <li><b>D </b> Day in year Number 189
266      * <li><b>d </b> Day in month Number 10
267      * <li><b>F </b> Day of week in month Number 2
268      * <li><b>E </b> Day in week Text Tuesday; Tue
269      * <li><b>a </b> Am/pm marker Text PM
270      * <li><b>H </b> Hour in day (0-23) Number 0
271      * <li><b>k </b> Hour in day (1-24) Number 24
272      * <li><b>K </b> Hour in am/pm (0-11) Number 0
273      * <li><b>h </b> Hour in am/pm (1-12) Number 12
274      * <li><b>m </b> Minute in hour Number 30
275      * <li><b>s </b> Second in minute Number 55
276      * <li><b>S </b> Millisecond Number 978
277      * <li><b>z </b> Time zone General time zone Pacific Standard Time; PST; GMT-08:00
278      * <li><b>Z </b> Time zone RFC 822 time zone -0800
279      * </ul>
280      * @jsp.attribute required="false" rtexprvalue="true"
281      */
282     public void setDatePattern(String pattern) {
283         this.datePattern = pattern;
284     }
285 
286     /**
287      * Set which date format shall be delivered. Does only apply for nodeDatas of type=Date. Language according to
288      * <code>java.util.Locale</code>.
289      * @jsp.attribute required="false" rtexprvalue="true"
290      */
291     public void setDateLanguage(String language) {
292         this.dateLanguage = language;
293     }
294 
295     /**
296      * Determines how line breaks are converted. Defaults to "<br />".
297      * Set to "" to have no line break at all, or any other value to be used as the line break.
298      * @jsp.attribute required="false" rtexprvalue="true"
299      */
300     public void setLineBreak(String lineBreak) {
301         this.lineBreak = lineBreak;
302     }
303 
304     protected String getFilePropertyValue(Content contentNode) {
305         NodeData nodeData = I18nContentSupportFactory.getI18nSupport().getNodeData(contentNode, this.getNodeDataName());
306         FileProperties props = new FileProperties(contentNode, nodeData.getName());
307         return props.getProperty(this.fileProperty);
308     }
309 
310     public int doEndTag() {
311         // don't reset any value set using a tag attribute here, or it will break any container that does tag pooling!
312 
313         Content contentNode = getFirstMatchingNode();
314         if (contentNode == null) {
315             return EVAL_PAGE;
316         }
317 
318         NodeData nodeData = I18nContentSupportFactory.getI18nSupport().getNodeData(contentNode, this.getNodeDataName());
319 
320         String value = null;
321 
322 
323         if (!nodeData.isExist()) {
324             // either a special case was passed in as the nodeDataName, or a bad value was passed in for the name
325             // - handle either case here
326             if(UUID_NODEDATANAME.equals(this.getNodeDataName())){
327                 value = contentNode.getUUID();
328             }
329             else if(PATH_NODEDATANAME.equals(this.getNodeDataName()) || HANDLE_NODEDATANAME.equals(this.getNodeDataName())){
330                 value = contentNode.getHandle();
331             }
332             else if(NODE_NAME_NODEDATANAME.equals(this.getNodeDataName())){
333                 value = contentNode.getName();
334             }
335             else if(StringUtils.isNotEmpty(this.getDefaultValue())){
336                 value = this.getDefaultValue();
337             }
338             else {
339                 return EVAL_PAGE;
340             }
341         }
342         else{
343             // the nodeData for the nodeDataName specified exists - determine how to output it according
344             // to its type, or any other variables that are set
345             int type = nodeData.getType();
346 
347             switch (type) {
348                 case PropertyType.DATE:
349 
350                     Date date = nodeData.getDate().getTime();
351                     if (date != null) {
352                         Locale locale;
353                         if (this.dateLanguage == null) {
354                              locale = I18nContentSupportFactory.getI18nSupport().getLocale();
355                         }
356                         else {
357                             locale = new Locale(this.dateLanguage);
358                         }
359                         value = DateUtil.format(date, this.datePattern, locale);
360                     }
361                     break;
362 
363                 case PropertyType.BINARY:
364                     value = this.getFilePropertyValue(contentNode);
365                     break;
366 
367                 default:
368                     value = StringUtils.isEmpty(this.lineBreak) ? nodeData.getString() : nodeData.getString(this.lineBreak);
369 
370                     // replace internal links that use the special magnolia link format (looks like ${link: {uuid: ... etc) -
371                     // ( - see info.magnolia.link.Link for an example of the special format that this next line
372                     //    handles )
373                     try {
374                         value = LinkUtil.convertLinksFromUUIDPattern(value, LinkTransformerManager.getInstance().getBrowserLink(MgnlContext.getAggregationState().getMainContent().getHandle())); // static actpage
375                     } catch (LinkException e) {
376                         log.warn("Failed to parse links with from " + nodeData.getName(), e);
377                     }
378 
379 
380                     if(!StringUtils.equalsIgnoreCase(getUuidToLink(), LINK_RESOLVING_NONE)){
381                         // if the uuidToLink type has been explicitly set, reset the output value
382                         // the link to the uuid value stored in the node - using whatever method
383                         // was specified in the uuidLinkType variable
384                         if(StringUtils.equals(this.getUuidToLink(), LINK_RESOLVING_HANDLE)){
385                             value = ContentUtil.uuid2path(this.getUuidToLinkRepository(), value);
386                         }
387                         else if(StringUtils.equals(this.getUuidToLink(), LINK_RESOLVING_ABSOLUTE)){
388                             try {
389                                 value = LinkUtil.convertUUIDtoURI(value, this.getUuidToLinkRepository());
390                             } catch (LinkException e) {
391                                 log.warn("Failed to parse links with from " + nodeData.getName(), e);
392                             }
393                         }
394                         else if(StringUtils.equals(this.getUuidToLink(), LINK_RESOLVING_RELATIVE)){
395                             try {
396                                 value = LinkUtil.makePathRelative(MgnlContext.getAggregationState().getMainContent().getHandle(), LinkFactory.createLink(this.getUuidToLinkRepository(), value).getHandle());
397                             } catch (LinkException e) {
398                                 log.warn("Failed to parse links with from " + nodeData.getName(), e);
399                             }
400                         }
401                         else{
402                             throw new IllegalArgumentException("not supported value for uuidToLink");
403                         }
404                     }
405                     break;
406             }
407         }
408 
409         value = StringUtils.defaultIfEmpty(value, this.getDefaultValue());
410 
411         if (var != null) {
412             // set result as a variable
413             pageContext.setAttribute(var, value, scope);
414         }
415         else if (value != null) {
416 
417             if ( escapeXml ) {
418                 value = StringEscapeUtils.escapeXml( value );
419             }
420 
421             JspWriter out = pageContext.getOut();
422             try {
423                 out.print(value);
424             }
425             catch (IOException e) {
426                 // should never happen
427                 throw new NestableRuntimeException(e);
428             }
429         }
430 
431         return EVAL_PAGE;
432     }
433 
434     public void release() {
435         super.release();
436 
437         this.fileProperty = StringUtils.EMPTY;
438         this.datePattern = DEFAULT_DATEPATTERN;
439         this.dateLanguage = null;
440         this.lineBreak = DEFAULT_LINEBREAK;
441         this.var = null;
442         this.scope = PageContext.PAGE_SCOPE;
443     }
444 
445     public String getUuidToLink() {
446         return this.uuidToLink;
447     }
448 
449 
450     /**
451      * Transform a uuid value to a link. The following values are supported:
452      * <ul>
453      *   <li>none: no uuid to link resolving. (default value)</li>
454      *   <li>absolute: Resolve to a absolute link using the repository to uri mapping feature.</li>
455      *   <li>handle: resolve to a absolute link but do not use the repository to uri mapping.</li>
456      *   <li>relative: resolve to relative path. Path is relative to current page.</li>
457      * </ul>
458      * @jsp.attribute required="false" rtexprvalue="true"
459      */
460     public void setUuidToLink(String uuidToLink) {
461         this.uuidToLink = uuidToLink;
462     }
463 
464     public String getUuidToLinkRepository() {
465         return this.uuidToLinkRepository;
466     }
467 
468     /**
469      * Used if the uuidToLink attribute is set. The content is found in this repository. Defaults to "website".
470      * @jsp.attribute required="false" rtexprvalue="true"
471      */
472     public void setUuidToLinkRepository(String uuidToLinkRepository) {
473         this.uuidToLinkRepository = uuidToLinkRepository;
474     }
475 
476     public String getDefaultValue() {
477         return this.defaultValue;
478     }
479 
480     /**
481      * Default value used if the expresion evaluates to null or an empty string.
482      * @jsp.attribute required="false" rtexprvalue="true"
483      */
484     public void setDefaultValue(String defaultValue) {
485         this.defaultValue = defaultValue;
486     }
487 
488 }