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