001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsPropertyDefinition; 033import org.opencms.flex.CmsFlexController; 034import org.opencms.flex.CmsFlexResponse; 035import org.opencms.loader.CmsJspLoader; 036import org.opencms.loader.CmsLoaderException; 037import org.opencms.loader.I_CmsResourceLoader; 038import org.opencms.loader.I_CmsResourceStringDumpLoader; 039import org.opencms.main.CmsException; 040import org.opencms.main.OpenCms; 041import org.opencms.staticexport.CmsLinkManager; 042import org.opencms.util.CmsCollectionsGenericWrapper; 043import org.opencms.util.CmsRequestUtil; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.workplace.editors.directedit.CmsDirectEditParams; 046 047import java.io.IOException; 048import java.util.HashMap; 049import java.util.Locale; 050import java.util.Map; 051import java.util.Set; 052 053import javax.servlet.ServletException; 054import javax.servlet.ServletRequest; 055import javax.servlet.ServletResponse; 056import javax.servlet.http.HttpServletRequest; 057import javax.servlet.http.HttpServletResponse; 058import javax.servlet.jsp.JspException; 059import javax.servlet.jsp.PageContext; 060import javax.servlet.jsp.tagext.BodyTagSupport; 061 062import com.google.common.collect.Maps; 063 064/** 065 * Implementation of the <code><cms:include/></code> tag, 066 * used to include another OpenCms managed resource in a JSP.<p> 067 * 068 * @since 6.0.0 069 */ 070public class CmsJspTagInclude extends BodyTagSupport implements I_CmsJspTagParamParent { 071 072 /** Serial version UID required for safe serialization. */ 073 private static final long serialVersionUID = 705978510743164951L; 074 075 /** The value of the "attribute" attribute. */ 076 private String m_attribute; 077 078 /** The value of the "cacheable" attribute. */ 079 private boolean m_cacheable; 080 081 /** The value of the "editable" attribute. */ 082 private boolean m_editable; 083 084 /** The value of the "element" attribute. */ 085 private String m_element; 086 087 /** Map to save parameters to the include in. */ 088 private Map<String, String[]> m_parameterMap; 089 090 /** The value of the "property" attribute. */ 091 private String m_property; 092 093 /** The value of the "suffix" attribute. */ 094 private String m_suffix; 095 096 /** The value of the "page" attribute. */ 097 private String m_target; 098 099 /** 100 * Empty constructor, required for attribute value initialization.<p> 101 */ 102 public CmsJspTagInclude() { 103 104 super(); 105 m_cacheable = true; 106 } 107 108 /** 109 * Adds parameters to a parameter Map that can be used for a http request.<p> 110 * 111 * @param parameters the Map to add the parameters to 112 * @param name the name to add 113 * @param value the value to add 114 * @param overwrite if <code>true</code>, a parameter in the map will be overwritten by 115 * a parameter with the same name, otherwise the request will have multiple parameters 116 * with the same name (which is possible in http requests) 117 */ 118 public static void addParameter(Map<String, String[]> parameters, String name, String value, boolean overwrite) { 119 120 // No null values allowed in parameters 121 if ((parameters == null) || (name == null) || (value == null)) { 122 return; 123 } 124 125 // Check if the parameter name (key) exists 126 if (parameters.containsKey(name) && (!overwrite)) { 127 // Yes: Check name values if value exists, if so do nothing, else add new value 128 String[] values = parameters.get(name); 129 String[] newValues = new String[values.length + 1]; 130 System.arraycopy(values, 0, newValues, 0, values.length); 131 newValues[values.length] = value; 132 parameters.put(name, newValues); 133 } else { 134 // No: Add new parameter name / value pair 135 String[] values = new String[] {value}; 136 parameters.put(name, values); 137 } 138 } 139 140 /** 141 * Includes the selected target.<p> 142 * 143 * @param context the current JSP page context 144 * @param target the target for the include, might be <code>null</code> 145 * @param element the element to select form the target might be <code>null</code> 146 * @param editable flag to indicate if the target is editable 147 * @param paramMap a map of parameters for the include, will be merged with the request 148 * parameters, might be <code>null</code> 149 * @param attrMap a map of attributes for the include, will be merged with the request 150 * attributes, might be <code>null</code> 151 * @param req the current request 152 * @param res the current response 153 * 154 * @throws JspException in case something goes wrong 155 */ 156 public static void includeTagAction( 157 PageContext context, 158 String target, 159 String element, 160 boolean editable, 161 Map<String, String[]> paramMap, 162 Map<String, Object> attrMap, 163 ServletRequest req, 164 ServletResponse res) 165 throws JspException { 166 167 // no locale and no cachable parameter are used by default 168 includeTagAction(context, target, element, null, editable, true, paramMap, attrMap, req, res); 169 } 170 171 /** 172 * Includes the selected target.<p> 173 * 174 * @param context the current JSP page context 175 * @param target the target for the include, might be <code>null</code> 176 * @param element the element to select form the target, might be <code>null</code> 177 * @param locale the locale to use for the selected element, might be <code>null</code> 178 * @param editable flag to indicate if the target is editable 179 * @param cacheable flag to indicate if the target should be cacheable in the Flex cache 180 * @param paramMap a map of parameters for the include, will be merged with the request 181 * parameters, might be <code>null</code> 182 * @param attrMap a map of attributes for the include, will be merged with the request 183 * attributes, might be <code>null</code> 184 * @param req the current request 185 * @param res the current response 186 * 187 * @throws JspException in case something goes wrong 188 */ 189 public static void includeTagAction( 190 PageContext context, 191 String target, 192 String element, 193 Locale locale, 194 boolean editable, 195 boolean cacheable, 196 Map<String, String[]> paramMap, 197 Map<String, Object> attrMap, 198 ServletRequest req, 199 ServletResponse res) 200 throws JspException { 201 202 // the Flex controller provides access to the internal OpenCms structures 203 CmsFlexController controller = CmsFlexController.getController(req); 204 205 if (target == null) { 206 // set target to default 207 target = controller.getCmsObject().getRequestContext().getUri(); 208 } 209 210 // resolve possible relative URI 211 target = CmsLinkManager.getAbsoluteUri(target, controller.getCurrentRequest().getElementUri()); 212 213 try { 214 // check if the target actually exists in the OpenCms VFS 215 controller.getCmsObject().readResource(target); 216 } catch (CmsException e) { 217 // store exception in controller and discontinue 218 controller.setThrowable(e, target); 219 throw new JspException(e); 220 } 221 222 // include direct edit "start" element (if enabled) 223 boolean directEditOpen = editable 224 && CmsJspTagEditable.startDirectEdit(context, new CmsDirectEditParams(target, element)); 225 226 // save old parameters from request 227 Map<String, String[]> oldParameterMap = CmsCollectionsGenericWrapper.map(req.getParameterMap()); 228 try { 229 // each include will have it's unique map of parameters 230 Map<String, String[]> parameterMap = (paramMap == null) 231 ? new HashMap<String, String[]>() 232 : new HashMap<String, String[]>(paramMap); 233 if (cacheable && (element != null)) { 234 // add template element selector for JSP templates (only required if cacheable) 235 addParameter(parameterMap, I_CmsResourceLoader.PARAMETER_ELEMENT, element, true); 236 } 237 // add parameters to set the correct element 238 controller.getCurrentRequest().addParameterMap(parameterMap); 239 // each include will have it's unique map of attributes 240 Map<String, Object> attributeMap = (attrMap == null) 241 ? new HashMap<String, Object>() 242 : new HashMap<String, Object>(attrMap); 243 // add attributes to set the correct element 244 controller.getCurrentRequest().addAttributeMap(attributeMap); 245 Set<String> dynamicParams = controller.getCurrentRequest().getDynamicParameters(); 246 Map<String, String[]> extendedParameterMap = null; 247 if (!dynamicParams.isEmpty()) { 248 // We want to store the parameters from the request with keys in dynamicParams in the flex response's include list, but only if they're set 249 extendedParameterMap = Maps.newHashMap(); 250 extendedParameterMap.putAll(parameterMap); 251 for (String dynamicParam : dynamicParams) { 252 String[] val = req.getParameterMap().get(dynamicParam); 253 if (val != null) { 254 extendedParameterMap.put(dynamicParam, val); 255 } 256 } 257 } 258 if (cacheable) { 259 // use include with cache 260 includeActionWithCache( 261 controller, 262 context, 263 target, 264 extendedParameterMap != null ? extendedParameterMap : parameterMap, 265 attributeMap, 266 req, 267 res); 268 } else { 269 // no cache required 270 includeActionNoCache(controller, context, target, element, locale, req, res); 271 } 272 } finally { 273 // restore old parameter map (if required) 274 if (oldParameterMap != null) { 275 controller.getCurrentRequest().setParameterMap(oldParameterMap); 276 } 277 } 278 279 // include direct edit "end" element (if required) 280 if (directEditOpen) { 281 CmsJspTagEditable.endDirectEdit(context); 282 } 283 } 284 285 /** 286 * Includes the selected target without caching.<p> 287 * 288 * @param controller the current JSP controller 289 * @param context the current JSP page context 290 * @param target the target for the include 291 * @param element the element to select form the target 292 * @param locale the locale to select from the target 293 * @param req the current request 294 * @param res the current response 295 * 296 * @throws JspException in case something goes wrong 297 */ 298 private static void includeActionNoCache( 299 CmsFlexController controller, 300 PageContext context, 301 String target, 302 String element, 303 Locale locale, 304 ServletRequest req, 305 ServletResponse res) 306 throws JspException { 307 308 try { 309 // include is not cachable 310 CmsFile file = controller.getCmsObject().readFile(target); 311 CmsObject cms = controller.getCmsObject(); 312 if (locale == null) { 313 locale = cms.getRequestContext().getLocale(); 314 } 315 // get the loader for the requested file 316 I_CmsResourceLoader loader = OpenCms.getResourceManager().getLoader(file); 317 String content; 318 if (loader instanceof I_CmsResourceStringDumpLoader) { 319 // loader can provide content as a String 320 I_CmsResourceStringDumpLoader strLoader = (I_CmsResourceStringDumpLoader)loader; 321 content = strLoader.dumpAsString(cms, file, element, locale, req, res); 322 } else { 323 if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)) { 324 // http type is required for loader (no refactoring to avoid changes to interface) 325 CmsLoaderException e = new CmsLoaderException( 326 Messages.get().container(Messages.ERR_BAD_REQUEST_RESPONSE_0)); 327 throw new JspException(e); 328 } 329 // get the bytes from the loader and convert them to a String 330 byte[] result = loader.dump( 331 cms, 332 file, 333 element, 334 locale, 335 (HttpServletRequest)req, 336 (HttpServletResponse)res); 337 338 String encoding; 339 if (loader instanceof CmsJspLoader) { 340 // in case of JSPs use the response encoding 341 if (res instanceof CmsFlexResponse) { 342 encoding = ((CmsFlexResponse)res).getEncoding(); 343 } else { 344 encoding = res.getCharacterEncoding(); 345 } 346 } else { 347 // use the encoding from the property or the system default if not available 348 encoding = cms.readPropertyObject( 349 file, 350 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 351 true).getValue(OpenCms.getSystemInfo().getDefaultEncoding()); 352 } 353 // If the included target issued a redirect null will be returned from loader 354 if (result == null) { 355 result = new byte[0]; 356 } 357 content = new String(result, encoding); 358 } 359 // write the content String to the JSP output writer 360 context.getOut().print(content); 361 362 } catch (ServletException e) { 363 // store original Exception in controller in order to display it later 364 Throwable t = (e.getRootCause() != null) ? e.getRootCause() : e; 365 t = controller.setThrowable(t, target); 366 throw new JspException(t); 367 } catch (IOException e) { 368 // store original Exception in controller in order to display it later 369 Throwable t = controller.setThrowable(e, target); 370 throw new JspException(t); 371 } catch (CmsException e) { 372 // store original Exception in controller in order to display it later 373 Throwable t = controller.setThrowable(e, target); 374 throw new JspException(t); 375 } 376 } 377 378 /** 379 * Includes the selected target using the Flex cache.<p> 380 * 381 * @param controller the current JSP controller 382 * @param context the current JSP page context 383 * @param target the target for the include, might be <code>null</code> 384 * @param parameterMap a map of parameters for the include 385 * @param attributeMap a map of request attributes for the include 386 * @param req the current request 387 * @param res the current response 388 * 389 * @throws JspException in case something goes wrong 390 */ 391 private static void includeActionWithCache( 392 CmsFlexController controller, 393 PageContext context, 394 String target, 395 Map<String, String[]> parameterMap, 396 Map<String, Object> attributeMap, 397 ServletRequest req, 398 ServletResponse res) 399 throws JspException { 400 401 try { 402 403 // add the target to the include list (the list will be initialized if it is currently empty) 404 controller.getCurrentResponse().addToIncludeList(target, parameterMap, attributeMap); 405 // now use the Flex dispatcher to include the target (this will also work for targets in the OpenCms VFS) 406 try { 407 controller.getCurrentRequest().getRequestDispatcher(target).include(req, res); 408 } finally { 409 // write out a FLEX_CACHE_DELIMITER char on the page, this is used as a parsing delimiter later 410 context.getOut().print(CmsFlexResponse.FLEX_CACHE_DELIMITER); 411 } 412 } catch (ServletException e) { 413 // store original Exception in controller in order to display it later 414 Throwable t = (e.getRootCause() != null) ? e.getRootCause() : e; 415 t = controller.setThrowable(t, target); 416 throw new JspException(t); 417 } catch (IOException e) { 418 // store original Exception in controller in order to display it later 419 Throwable t = controller.setThrowable(e, target); 420 throw new JspException(t); 421 } 422 } 423 424 /** 425 * This methods adds parameters to the current request.<p> 426 * 427 * Parameters added here will be treated like parameters from the 428 * HttpRequest on included pages.<p> 429 * 430 * Remember that the value for a parameter in a HttpRequest is a 431 * String array, not just a simple String. If a parameter added here does 432 * not already exist in the HttpRequest, it will be added. If a parameter 433 * exists, another value will be added to the array of values. If the 434 * value already exists for the parameter, nothing will be added, since a 435 * value can appear only once per parameter.<p> 436 * 437 * @param name the name to add 438 * @param value the value to add 439 * @see org.opencms.jsp.I_CmsJspTagParamParent#addParameter(String, String) 440 */ 441 public void addParameter(String name, String value) { 442 443 // No null values allowed in parameters 444 if ((name == null) || (value == null)) { 445 return; 446 } 447 448 // Check if internal map exists, create new one if not 449 if (m_parameterMap == null) { 450 m_parameterMap = new HashMap<String, String[]>(); 451 } 452 453 addParameter(m_parameterMap, name, value, false); 454 } 455 456 /** 457 * @return <code>EVAL_PAGE</code> 458 * 459 * @see javax.servlet.jsp.tagext.Tag#doEndTag() 460 * 461 * @throws JspException by interface default 462 */ 463 @Override 464 public int doEndTag() throws JspException { 465 466 ServletRequest req = pageContext.getRequest(); 467 ServletResponse res = pageContext.getResponse(); 468 469 if (CmsFlexController.isCmsRequest(req)) { 470 // this will always be true if the page is called through OpenCms 471 CmsObject cms = CmsFlexController.getCmsObject(req); 472 String target = null; 473 474 // try to find out what to do 475 if (m_target != null) { 476 // option 1: target is set with "page" or "file" parameter 477 target = m_target + getSuffix(); 478 } else if (m_property != null) { 479 // option 2: target is set with "property" parameter 480 try { 481 String prop = cms.readPropertyObject(cms.getRequestContext().getUri(), m_property, true).getValue(); 482 if (prop != null) { 483 target = prop + getSuffix(); 484 } 485 } catch (RuntimeException e) { 486 // target must be null 487 target = null; 488 } catch (Exception e) { 489 // target will be null 490 e = null; 491 } 492 } else if (m_attribute != null) { 493 // option 3: target is set in "attribute" parameter 494 try { 495 String attr = (String)req.getAttribute(m_attribute); 496 if (attr != null) { 497 target = attr + getSuffix(); 498 } 499 } catch (RuntimeException e) { 500 // target must be null 501 target = null; 502 } catch (Exception e) { 503 // target will be null 504 e = null; 505 } 506 } else { 507 // option 4: target might be set in body 508 String body = null; 509 if (getBodyContent() != null) { 510 body = getBodyContent().getString(); 511 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(body)) { 512 // target IS set in body 513 target = body + getSuffix(); 514 } 515 // else target is not set at all, default will be used 516 } 517 } 518 519 // now perform the include action 520 includeTagAction( 521 pageContext, 522 target, 523 m_element, 524 null, 525 m_editable, 526 m_cacheable, 527 m_parameterMap, 528 CmsRequestUtil.getAtrributeMap(req), 529 req, 530 res); 531 532 release(); 533 } 534 535 return EVAL_PAGE; 536 } 537 538 /** 539 * Returns <code>{@link #EVAL_BODY_BUFFERED}</code>.<p> 540 * 541 * @return <code>{@link #EVAL_BODY_BUFFERED}</code> 542 * 543 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 544 */ 545 @Override 546 public int doStartTag() { 547 548 return EVAL_BODY_BUFFERED; 549 } 550 551 /** 552 * Returns the attribute.<p> 553 * 554 * @return the attribute 555 */ 556 public String getAttribute() { 557 558 return m_attribute != null ? m_attribute : ""; 559 } 560 561 /** 562 * Returns the cacheable flag.<p> 563 * 564 * @return the cacheable flag 565 */ 566 public String getCacheable() { 567 568 return String.valueOf(m_cacheable); 569 } 570 571 /** 572 * Returns the editable flag.<p> 573 * 574 * @return the editable flag 575 */ 576 public String getEditable() { 577 578 return String.valueOf(m_editable); 579 } 580 581 /** 582 * Returns the element.<p> 583 * 584 * @return the element 585 */ 586 public String getElement() { 587 588 return m_element; 589 } 590 591 /** 592 * Returns the value of <code>{@link #getPage()}</code>.<p> 593 * 594 * @return the value of <code>{@link #getPage()}</code> 595 * @see #getPage() 596 */ 597 public String getFile() { 598 599 return getPage(); 600 } 601 602 /** 603 * Returns the include page target.<p> 604 * 605 * @return the include page target 606 */ 607 public String getPage() { 608 609 return m_target != null ? m_target : ""; 610 } 611 612 /** 613 * Returns the property.<p> 614 * 615 * @return the property 616 */ 617 public String getProperty() { 618 619 return m_property != null ? m_property : ""; 620 } 621 622 /** 623 * Returns the suffix.<p> 624 * 625 * @return the suffix 626 */ 627 public String getSuffix() { 628 629 return m_suffix != null ? m_suffix : ""; 630 } 631 632 /** 633 * @see javax.servlet.jsp.tagext.Tag#release() 634 */ 635 @Override 636 public void release() { 637 638 super.release(); 639 m_target = null; 640 m_suffix = null; 641 m_property = null; 642 m_element = null; 643 m_parameterMap = null; 644 m_editable = false; 645 m_cacheable = true; 646 } 647 648 /** 649 * Sets the attribute.<p> 650 * 651 * @param attribute the attribute to set 652 */ 653 public void setAttribute(String attribute) { 654 655 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(attribute)) { 656 m_attribute = attribute; 657 } 658 } 659 660 /** 661 * Sets the cacheable flag.<p> 662 * 663 * Cachable is <code>true</code> by default.<p> 664 * 665 * @param cacheable the flag to set 666 */ 667 public void setCacheable(String cacheable) { 668 669 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(cacheable)) { 670 m_cacheable = Boolean.valueOf(cacheable).booleanValue(); 671 } 672 } 673 674 /** 675 * Sets the editable flag.<p> 676 * 677 * Editable is <code>false</code> by default.<p> 678 * 679 * @param editable the flag to set 680 */ 681 public void setEditable(String editable) { 682 683 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(editable)) { 684 m_editable = Boolean.valueOf(editable).booleanValue(); 685 } 686 } 687 688 /** 689 * Sets the element.<p> 690 * 691 * @param element the element to set 692 */ 693 public void setElement(String element) { 694 695 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(element)) { 696 m_element = element; 697 } 698 } 699 700 /** 701 * Sets the file, same as using <code>setPage()</code>.<p> 702 * 703 * @param file the file to set 704 * @see #setPage(String) 705 */ 706 public void setFile(String file) { 707 708 setPage(file); 709 } 710 711 /** 712 * Sets the include page target.<p> 713 * 714 * @param target the target to set 715 */ 716 public void setPage(String target) { 717 718 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(target)) { 719 m_target = target; 720 } 721 } 722 723 /** 724 * Sets the property.<p> 725 * 726 * @param property the property to set 727 */ 728 public void setProperty(String property) { 729 730 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(property)) { 731 m_property = property; 732 } 733 } 734 735 /** 736 * Sets the suffix.<p> 737 * 738 * @param suffix the suffix to set 739 */ 740 public void setSuffix(String suffix) { 741 742 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(suffix)) { 743 m_suffix = suffix.toLowerCase(); 744 } 745 } 746}