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 }