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 encoding = res.getCharacterEncoding(); 342 } else { 343 // use the encoding from the property or the system default if not available 344 encoding = cms.readPropertyObject( 345 file, 346 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 347 true).getValue(OpenCms.getSystemInfo().getDefaultEncoding()); 348 } 349 // If the included target issued a redirect null will be returned from loader 350 if (result == null) { 351 result = new byte[0]; 352 } 353 content = new String(result, encoding); 354 } 355 // write the content String to the JSP output writer 356 context.getOut().print(content); 357 358 } catch (ServletException e) { 359 // store original Exception in controller in order to display it later 360 Throwable t = (e.getRootCause() != null) ? e.getRootCause() : e; 361 t = controller.setThrowable(t, target); 362 throw new JspException(t); 363 } catch (IOException e) { 364 // store original Exception in controller in order to display it later 365 Throwable t = controller.setThrowable(e, target); 366 throw new JspException(t); 367 } catch (CmsException 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 } 372 } 373 374 /** 375 * Includes the selected target using the Flex cache.<p> 376 * 377 * @param controller the current JSP controller 378 * @param context the current JSP page context 379 * @param target the target for the include, might be <code>null</code> 380 * @param parameterMap a map of parameters for the include 381 * @param attributeMap a map of request attributes for the include 382 * @param req the current request 383 * @param res the current response 384 * 385 * @throws JspException in case something goes wrong 386 */ 387 private static void includeActionWithCache( 388 CmsFlexController controller, 389 PageContext context, 390 String target, 391 Map<String, String[]> parameterMap, 392 Map<String, Object> attributeMap, 393 ServletRequest req, 394 ServletResponse res) 395 throws JspException { 396 397 try { 398 399 // add the target to the include list (the list will be initialized if it is currently empty) 400 controller.getCurrentResponse().addToIncludeList(target, parameterMap, attributeMap); 401 // now use the Flex dispatcher to include the target (this will also work for targets in the OpenCms VFS) 402 controller.getCurrentRequest().getRequestDispatcher(target).include(req, res); 403 // write out a FLEX_CACHE_DELIMITER char on the page, this is used as a parsing delimiter later 404 context.getOut().print(CmsFlexResponse.FLEX_CACHE_DELIMITER); 405 } catch (ServletException e) { 406 // store original Exception in controller in order to display it later 407 Throwable t = (e.getRootCause() != null) ? e.getRootCause() : e; 408 t = controller.setThrowable(t, target); 409 throw new JspException(t); 410 } catch (IOException e) { 411 // store original Exception in controller in order to display it later 412 Throwable t = controller.setThrowable(e, target); 413 throw new JspException(t); 414 } 415 } 416 417 /** 418 * This methods adds parameters to the current request.<p> 419 * 420 * Parameters added here will be treated like parameters from the 421 * HttpRequest on included pages.<p> 422 * 423 * Remember that the value for a parameter in a HttpRequest is a 424 * String array, not just a simple String. If a parameter added here does 425 * not already exist in the HttpRequest, it will be added. If a parameter 426 * exists, another value will be added to the array of values. If the 427 * value already exists for the parameter, nothing will be added, since a 428 * value can appear only once per parameter.<p> 429 * 430 * @param name the name to add 431 * @param value the value to add 432 * @see org.opencms.jsp.I_CmsJspTagParamParent#addParameter(String, String) 433 */ 434 public void addParameter(String name, String value) { 435 436 // No null values allowed in parameters 437 if ((name == null) || (value == null)) { 438 return; 439 } 440 441 // Check if internal map exists, create new one if not 442 if (m_parameterMap == null) { 443 m_parameterMap = new HashMap<String, String[]>(); 444 } 445 446 addParameter(m_parameterMap, name, value, false); 447 } 448 449 /** 450 * @return <code>EVAL_PAGE</code> 451 * 452 * @see javax.servlet.jsp.tagext.Tag#doEndTag() 453 * 454 * @throws JspException by interface default 455 */ 456 @Override 457 public int doEndTag() throws JspException { 458 459 ServletRequest req = pageContext.getRequest(); 460 ServletResponse res = pageContext.getResponse(); 461 462 if (CmsFlexController.isCmsRequest(req)) { 463 // this will always be true if the page is called through OpenCms 464 CmsObject cms = CmsFlexController.getCmsObject(req); 465 String target = null; 466 467 // try to find out what to do 468 if (m_target != null) { 469 // option 1: target is set with "page" or "file" parameter 470 target = m_target + getSuffix(); 471 } else if (m_property != null) { 472 // option 2: target is set with "property" parameter 473 try { 474 String prop = cms.readPropertyObject(cms.getRequestContext().getUri(), m_property, true).getValue(); 475 if (prop != null) { 476 target = prop + getSuffix(); 477 } 478 } catch (RuntimeException e) { 479 // target must be null 480 target = null; 481 } catch (Exception e) { 482 // target will be null 483 e = null; 484 } 485 } else if (m_attribute != null) { 486 // option 3: target is set in "attribute" parameter 487 try { 488 String attr = (String)req.getAttribute(m_attribute); 489 if (attr != null) { 490 target = attr + getSuffix(); 491 } 492 } catch (RuntimeException e) { 493 // target must be null 494 target = null; 495 } catch (Exception e) { 496 // target will be null 497 e = null; 498 } 499 } else { 500 // option 4: target might be set in body 501 String body = null; 502 if (getBodyContent() != null) { 503 body = getBodyContent().getString(); 504 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(body)) { 505 // target IS set in body 506 target = body + getSuffix(); 507 } 508 // else target is not set at all, default will be used 509 } 510 } 511 512 // now perform the include action 513 includeTagAction( 514 pageContext, 515 target, 516 m_element, 517 null, 518 m_editable, 519 m_cacheable, 520 m_parameterMap, 521 CmsRequestUtil.getAtrributeMap(req), 522 req, 523 res); 524 525 release(); 526 } 527 528 return EVAL_PAGE; 529 } 530 531 /** 532 * Returns <code>{@link #EVAL_BODY_BUFFERED}</code>.<p> 533 * 534 * @return <code>{@link #EVAL_BODY_BUFFERED}</code> 535 * 536 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 537 */ 538 @Override 539 public int doStartTag() { 540 541 return EVAL_BODY_BUFFERED; 542 } 543 544 /** 545 * Returns the attribute.<p> 546 * 547 * @return the attribute 548 */ 549 public String getAttribute() { 550 551 return m_attribute != null ? m_attribute : ""; 552 } 553 554 /** 555 * Returns the cacheable flag.<p> 556 * 557 * @return the cacheable flag 558 */ 559 public String getCacheable() { 560 561 return String.valueOf(m_cacheable); 562 } 563 564 /** 565 * Returns the editable flag.<p> 566 * 567 * @return the editable flag 568 */ 569 public String getEditable() { 570 571 return String.valueOf(m_editable); 572 } 573 574 /** 575 * Returns the element.<p> 576 * 577 * @return the element 578 */ 579 public String getElement() { 580 581 return m_element; 582 } 583 584 /** 585 * Returns the value of <code>{@link #getPage()}</code>.<p> 586 * 587 * @return the value of <code>{@link #getPage()}</code> 588 * @see #getPage() 589 */ 590 public String getFile() { 591 592 return getPage(); 593 } 594 595 /** 596 * Returns the include page target.<p> 597 * 598 * @return the include page target 599 */ 600 public String getPage() { 601 602 return m_target != null ? m_target : ""; 603 } 604 605 /** 606 * Returns the property.<p> 607 * 608 * @return the property 609 */ 610 public String getProperty() { 611 612 return m_property != null ? m_property : ""; 613 } 614 615 /** 616 * Returns the suffix.<p> 617 * 618 * @return the suffix 619 */ 620 public String getSuffix() { 621 622 return m_suffix != null ? m_suffix : ""; 623 } 624 625 /** 626 * @see javax.servlet.jsp.tagext.Tag#release() 627 */ 628 @Override 629 public void release() { 630 631 super.release(); 632 m_target = null; 633 m_suffix = null; 634 m_property = null; 635 m_element = null; 636 m_parameterMap = null; 637 m_editable = false; 638 m_cacheable = true; 639 } 640 641 /** 642 * Sets the attribute.<p> 643 * 644 * @param attribute the attribute to set 645 */ 646 public void setAttribute(String attribute) { 647 648 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(attribute)) { 649 m_attribute = attribute; 650 } 651 } 652 653 /** 654 * Sets the cacheable flag.<p> 655 * 656 * Cachable is <code>true</code> by default.<p> 657 * 658 * @param cacheable the flag to set 659 */ 660 public void setCacheable(String cacheable) { 661 662 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(cacheable)) { 663 m_cacheable = Boolean.valueOf(cacheable).booleanValue(); 664 } 665 } 666 667 /** 668 * Sets the editable flag.<p> 669 * 670 * Editable is <code>false</code> by default.<p> 671 * 672 * @param editable the flag to set 673 */ 674 public void setEditable(String editable) { 675 676 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(editable)) { 677 m_editable = Boolean.valueOf(editable).booleanValue(); 678 } 679 } 680 681 /** 682 * Sets the element.<p> 683 * 684 * @param element the element to set 685 */ 686 public void setElement(String element) { 687 688 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(element)) { 689 m_element = element; 690 } 691 } 692 693 /** 694 * Sets the file, same as using <code>setPage()</code>.<p> 695 * 696 * @param file the file to set 697 * @see #setPage(String) 698 */ 699 public void setFile(String file) { 700 701 setPage(file); 702 } 703 704 /** 705 * Sets the include page target.<p> 706 * 707 * @param target the target to set 708 */ 709 public void setPage(String target) { 710 711 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(target)) { 712 m_target = target; 713 } 714 } 715 716 /** 717 * Sets the property.<p> 718 * 719 * @param property the property to set 720 */ 721 public void setProperty(String property) { 722 723 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(property)) { 724 m_property = property; 725 } 726 } 727 728 /** 729 * Sets the suffix.<p> 730 * 731 * @param suffix the suffix to set 732 */ 733 public void setSuffix(String suffix) { 734 735 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(suffix)) { 736 m_suffix = suffix.toLowerCase(); 737 } 738 } 739}