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 }