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.collectors.I_CmsResourceCollector; 032import org.opencms.flex.CmsFlexController; 033import org.opencms.i18n.CmsEncoder; 034import org.opencms.i18n.CmsLocaleManager; 035import org.opencms.jsp.util.CmsJspContentLoadBean; 036import org.opencms.loader.CmsDefaultFileNameGenerator; 037import org.opencms.main.CmsException; 038import org.opencms.main.CmsIllegalArgumentException; 039import org.opencms.main.OpenCms; 040import org.opencms.util.CmsMacroResolver; 041import org.opencms.util.CmsStringUtil; 042import org.opencms.workplace.editors.directedit.CmsDirectEditButtonSelection; 043import org.opencms.workplace.editors.directedit.CmsDirectEditMode; 044import org.opencms.workplace.editors.directedit.CmsDirectEditParams; 045import org.opencms.xml.I_CmsXmlDocument; 046import org.opencms.xml.content.CmsXmlContentFactory; 047 048import java.util.Iterator; 049import java.util.Locale; 050 051import javax.servlet.jsp.JspException; 052import javax.servlet.jsp.PageContext; 053import javax.servlet.jsp.tagext.Tag; 054 055/** 056 * Implementation of the <code><cms:contentload/></code> tag, 057 * used to access and display XML content item information from the VFS.<p> 058 * 059 * Since version 7.0.2 it is also possible to store the results of the content load in the JSP context 060 * using a {@link CmsJspContentLoadBean}. Using this bean the loaded XML content objects can be accessed 061 * directly using the JSP EL and the JSTL. To use this feature, you need to add the <code>var</code> (and optionally 062 * the <code>scope</code>) parameter to the content load tag. For example, if a parameter like 063 * <code>var="myVarName"</code> is provided, then the result of the content load is stored in the JSP 064 * context variable <code>myVarName</code> with an instance of a {@link CmsJspContentLoadBean}.<p> 065 * 066 * @since 6.0.0 067 */ 068public class CmsJspTagContentLoad extends CmsJspTagResourceLoad implements I_CmsXmlContentContainer { 069 070 /** Serial version UID required for safe serialization. */ 071 private static final long serialVersionUID = 981176995635225294L; 072 073 /** Reference to the last loaded content element. */ 074 private transient I_CmsXmlDocument m_content; 075 076 /** 077 * The locale to use for displaying the current content.<p> 078 * 079 * Initially, this is equal to the locale set using <code>{@link #setLocale(String)}</code>. 080 * However, the content locale may change in case a loaded XML content does not have the selected locale available. 081 * In this case the next default locale that is available in the content will be used as content locale.<p> 082 */ 083 private Locale m_contentLocale; 084 085 /** The "direct edit" button selection to use for the 2nd to the last element. */ 086 private CmsDirectEditButtonSelection m_directEditFollowButtons; 087 088 /** The link for creation of a new element, specified by the selected collector. */ 089 private String m_directEditLinkForNew; 090 091 /** The direct edit mode. */ 092 private CmsDirectEditMode m_directEditMode; 093 094 /** Indicates if the last element was direct editable. */ 095 private boolean m_directEditOpen; 096 097 /** The edit empty tag attribute. */ 098 private boolean m_editEmpty; 099 100 /** Indicates if this is the first content iteration loop. */ 101 private boolean m_isFirstLoop; 102 103 /** Reference to the currently selected locale. */ 104 private Locale m_locale; 105 106 /** Post-create handler class. */ 107 private String m_postCreateHandler; 108 109 /** 110 * Empty constructor, required for JSP tags.<p> 111 */ 112 public CmsJspTagContentLoad() { 113 114 super(); 115 } 116 117 /** 118 * Constructor used when using <code>contentload</code> from scriptlet code.<p> 119 * 120 * @param container the parent content container (could be a preloader) 121 * @param context the JSP page context 122 * @param collectorName the collector name to use 123 * @param collectorParam the collector param to use 124 * @param locale the locale to use 125 * @param editable indicates if "direct edit" support is wanted 126 * 127 * @throws JspException in case something goes wrong 128 */ 129 public CmsJspTagContentLoad( 130 I_CmsXmlContentContainer container, 131 PageContext context, 132 String collectorName, 133 String collectorParam, 134 Locale locale, 135 boolean editable) 136 throws JspException { 137 138 this(container, context, collectorName, collectorParam, null, null, locale, editable); 139 } 140 141 /** 142 * Constructor used when using <code>contentload</code> from scriptlet code.<p> 143 * 144 * @param container the parent content container (could be a preloader) 145 * @param context the JSP page context 146 * @param collectorName the collector name to use 147 * @param collectorParam the collector param to use 148 * @param pageIndex the display page index (may contain macros) 149 * @param pageSize the display page size (may contain macros) 150 * @param locale the locale to use 151 * @param editable indicates if "direct edit" support is wanted 152 * 153 * @throws JspException in case something goes wrong 154 */ 155 public CmsJspTagContentLoad( 156 I_CmsXmlContentContainer container, 157 PageContext context, 158 String collectorName, 159 String collectorParam, 160 String pageIndex, 161 String pageSize, 162 Locale locale, 163 boolean editable) 164 throws JspException { 165 166 this( 167 container, 168 context, 169 collectorName, 170 collectorParam, 171 pageIndex, 172 pageSize, 173 locale, 174 CmsDirectEditMode.valueOf(editable)); 175 } 176 177 /** 178 * Constructor used when using <code>contentload</code> from scriptlet code.<p> 179 * 180 * @param container the parent content container (could be a preloader) 181 * @param context the JSP page context 182 * @param collectorName the collector name to use 183 * @param collectorParam the collector param to use 184 * @param pageIndex the display page index (may contain macros) 185 * @param pageSize the display page size (may contain macros) 186 * @param locale the locale to use 187 * @param editMode indicates which "direct edit" mode is wanted 188 * 189 * @throws JspException in case something goes wrong 190 */ 191 public CmsJspTagContentLoad( 192 I_CmsXmlContentContainer container, 193 PageContext context, 194 String collectorName, 195 String collectorParam, 196 String pageIndex, 197 String pageSize, 198 Locale locale, 199 CmsDirectEditMode editMode) 200 throws JspException { 201 202 setCollector(collectorName); 203 setParam(collectorParam); 204 setPageIndex(pageIndex); 205 setPageSize(pageSize); 206 m_locale = locale; 207 m_contentLocale = locale; 208 m_directEditMode = editMode; 209 m_preload = false; 210 211 setPageContext(context); 212 init(container); 213 } 214 215 /** 216 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 217 */ 218 @Override 219 public int doStartTag() throws JspException, CmsIllegalArgumentException { 220 221 // get a reference to the parent "content container" class (if available) 222 Tag ancestor = findAncestorWithClass(this, I_CmsXmlContentContainer.class); 223 I_CmsXmlContentContainer container = null; 224 if (ancestor != null) { 225 // parent content container available, use preloaded values from this container 226 container = (I_CmsXmlContentContainer)ancestor; 227 // check if container really is a preloader 228 if (!container.isPreloader()) { 229 // don't use ancestor if not a preloader 230 container = null; 231 } 232 } 233 234 // initialize the content load tag 235 init(container); 236 237 hasMoreResources(); 238 239 return isScopeVarSet() ? SKIP_BODY : EVAL_BODY_INCLUDE; 240 } 241 242 /** 243 * Returns the editable flag.<p> 244 * 245 * @return the editable flag 246 */ 247 public String getEditable() { 248 249 return m_directEditMode != null ? m_directEditMode.toString() : ""; 250 } 251 252 /** 253 * Returns the locale.<p> 254 * 255 * @return the locale 256 */ 257 public String getLocale() { 258 259 return (m_locale != null) ? m_locale.toString() : ""; 260 } 261 262 /** 263 * @see org.opencms.jsp.I_CmsXmlContentContainer#getXmlDocument() 264 */ 265 public I_CmsXmlDocument getXmlDocument() { 266 267 return m_content; 268 } 269 270 /** 271 * @see org.opencms.jsp.I_CmsXmlContentContainer#getXmlDocumentElement() 272 */ 273 public String getXmlDocumentElement() { 274 275 // value must be set in "loop" or "show" class 276 return null; 277 } 278 279 /** 280 * @see org.opencms.jsp.I_CmsXmlContentContainer#getXmlDocumentLocale() 281 */ 282 public Locale getXmlDocumentLocale() { 283 284 return m_contentLocale; 285 } 286 287 /** 288 * @see org.opencms.jsp.I_CmsXmlContentContainer#hasMoreResources() 289 */ 290 @Override 291 public boolean hasMoreResources() throws JspException { 292 293 // check if there are more files to iterate 294 boolean hasMoreContent = m_collectorResult.size() > 0; 295 if (m_isFirstLoop) { 296 297 if (!m_cms.getRequestContext().getCurrentProject().isOnlineProject() && (m_collector != null)) { 298 CmsContentLoadCollectorInfo info = new CmsContentLoadCollectorInfo(); 299 info.setCollectorName(m_collectorName); 300 info.setCollectorParams(m_collectorParam); 301 info.setId(m_contentInfoBean.getId()); 302 if (CmsJspTagEditable.getDirectEditProvider(pageContext) != null) { 303 CmsJspTagEditable.getDirectEditProvider(pageContext).insertDirectEditListMetadata( 304 pageContext, 305 info); 306 } 307 } 308 m_isFirstLoop = false; 309 if (!hasMoreContent && m_editEmpty && (m_directEditLinkForNew != null)) { 310 try { 311 CmsJspTagEditable.insertEditEmpty(pageContext, this, m_directEditMode, m_contentInfoBean.getId()); 312 } catch (CmsException e) { 313 throw new JspException(e); 314 } 315 } 316 } else { 317 if (m_directEditOpen) { 318 // last element was direct editable, close it 319 CmsJspTagEditable.endDirectEdit(pageContext); 320 m_directEditOpen = false; 321 } 322 } 323 324 if (isPreloader()) { 325 // if in preload mode, no result is required 326 return false; 327 } 328 329 if (hasMoreContent) { 330 // there are more results available... 331 try { 332 doLoadNextFile(); 333 } catch (CmsException e) { 334 m_controller.setThrowable(e, m_resourceName); 335 throw new JspException(e); 336 } 337 338 // check "direct edit" support 339 if (m_directEditMode.isEnabled() && (m_resourceName != null)) { 340 341 // check options for first element 342 CmsDirectEditButtonSelection directEditButtons; 343 if (m_directEditFollowButtons == null) { 344 // this is the first call, calculate the options 345 if (m_directEditLinkForNew == null) { 346 // if create link is null, show only "edit" and "delete" button for first element 347 directEditButtons = CmsDirectEditButtonSelection.EDIT_DELETE; 348 m_directEditFollowButtons = directEditButtons; 349 } else { 350 // if create link is not null, show "edit", "delete" and "new" buttons 351 directEditButtons = CmsDirectEditButtonSelection.EDIT_DELETE_NEW; 352 m_directEditFollowButtons = CmsDirectEditButtonSelection.EDIT_DELETE_NEW; 353 } 354 } else { 355 // re-use pre calculated options 356 directEditButtons = m_directEditFollowButtons; 357 } 358 359 CmsDirectEditParams params = new CmsDirectEditParams( 360 m_resourceName, 361 directEditButtons, 362 m_directEditMode, 363 m_directEditLinkForNew); 364 params.setPostCreateHandler(m_postCreateHandler); 365 params.setId(m_contentInfoBean.getId()); 366 params.setCollectorName(m_collectorName); 367 params.setCollectorParams(m_param); 368 m_directEditOpen = CmsJspTagEditable.startDirectEdit(pageContext, params); 369 } 370 371 } else { 372 // no more results in the collector, reset locale (just to make sure...) 373 m_locale = null; 374 m_editEmpty = false; 375 } 376 377 return hasMoreContent; 378 } 379 380 /** 381 * Returns the edit empty attribute.<p> 382 * 383 * @return the edit empty attribute 384 */ 385 public boolean isEditEmpty() { 386 387 return m_editEmpty; 388 } 389 390 /** 391 * @see javax.servlet.jsp.tagext.Tag#release() 392 */ 393 @Override 394 public void release() { 395 396 m_content = null; 397 m_contentLocale = null; 398 m_directEditLinkForNew = null; 399 m_directEditFollowButtons = null; 400 m_directEditOpen = false; 401 m_directEditMode = null; 402 m_isFirstLoop = false; 403 m_locale = null; 404 super.release(); 405 } 406 407 /** 408 * Sets the editable mode.<p> 409 * 410 * @param mode the mode to set 411 */ 412 public void setEditable(String mode) { 413 414 m_directEditMode = CmsDirectEditMode.valueOf(mode); 415 } 416 417 /** 418 * Sets the edit empty attribute.<p> 419 * 420 * @param editEmpty the edit empty attribute to set 421 */ 422 public void setEditEmpty(boolean editEmpty) { 423 424 m_editEmpty = editEmpty; 425 } 426 427 /** 428 * Sets the locale.<p> 429 * 430 * @param locale the locale to set 431 */ 432 public void setLocale(String locale) { 433 434 if (CmsStringUtil.isEmpty(locale)) { 435 m_locale = null; 436 m_contentLocale = null; 437 } else { 438 m_locale = CmsLocaleManager.getLocale(locale); 439 m_contentLocale = m_locale; 440 } 441 } 442 443 /** 444 * Sets the post-create handler class name.<p> 445 * 446 * @param postCreateHandler the post-create handler class name 447 */ 448 public void setPostCreateHandler(String postCreateHandler) { 449 450 m_postCreateHandler = postCreateHandler; 451 } 452 453 /** 454 * Load the next file name from the initialized list of file names.<p> 455 * 456 * @throws CmsException if something goes wrong 457 */ 458 protected void doLoadNextFile() throws CmsException { 459 460 super.doLoadNextResource(); 461 if (m_resource == null) { 462 return; 463 } 464 465 // upgrade the resource to a file 466 CmsFile file = m_cms.readFile(m_resource); 467 468 // unmarshal the XML content from the resource, don't use unmarshal(CmsObject, CmsResource) 469 // as no support for getting the historic version that has been cached by a CmsHistoryResourceHandler 470 // will come from there! 471 m_content = CmsXmlContentFactory.unmarshal(m_cms, file, pageContext.getRequest()); 472 473 // check if locale is available 474 m_contentLocale = m_locale; 475 if (!m_content.hasLocale(m_contentLocale)) { 476 Iterator<Locale> it = OpenCms.getLocaleManager().getDefaultLocales().iterator(); 477 while (it.hasNext()) { 478 Locale locale = it.next(); 479 if (m_content.hasLocale(locale)) { 480 // found a matching locale 481 m_contentLocale = locale; 482 break; 483 } 484 } 485 } 486 } 487 488 /** 489 * Initializes this content load tag.<p> 490 * 491 * @param container the parent container (could be a preloader) 492 * 493 * @throws JspException in case something goes wrong 494 */ 495 protected void init(I_CmsXmlContentContainer container) throws JspException { 496 497 // check if the tag contains a pageSize, pageIndex and pageNavLength attribute, or none of them 498 int pageAttribCount = 0; 499 pageAttribCount += CmsStringUtil.isNotEmpty(m_pageSize) ? 1 : 0; 500 pageAttribCount += CmsStringUtil.isNotEmpty(m_pageIndex) ? 1 : 0; 501 502 if ((pageAttribCount > 0) && (pageAttribCount < 2)) { 503 throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_TAG_CONTENTLOAD_INDEX_SIZE_0)); 504 } 505 506 I_CmsXmlContentContainer usedContainer; 507 if (container == null) { 508 // no preloading ancestor has been found 509 usedContainer = this; 510 if (CmsStringUtil.isEmpty(m_collector)) { 511 // check if the tag contains a collector attribute 512 throw new CmsIllegalArgumentException( 513 Messages.get().container(Messages.ERR_TAG_CONTENTLOAD_MISSING_COLLECTOR_0)); 514 } 515 if (CmsStringUtil.isEmpty(m_param)) { 516 // check if the tag contains a param attribute 517 throw new CmsIllegalArgumentException( 518 Messages.get().container(Messages.ERR_TAG_CONTENTLOAD_MISSING_PARAM_0)); 519 } 520 } else { 521 // use provided container (preloading ancestor) 522 usedContainer = container; 523 } 524 525 if (isPreloader()) { 526 // always disable direct edit for preload 527 m_directEditMode = CmsDirectEditMode.FALSE; 528 } else if (m_directEditMode == null) { 529 // direct edit mode must not be null 530 m_directEditMode = CmsDirectEditMode.FALSE; 531 } 532 533 // initialize OpenCms access objects 534 m_controller = CmsFlexController.getController(pageContext.getRequest()); 535 m_cms = m_controller.getCmsObject(); 536 537 // get the resource name from the selected container 538 String resourcename = getResourceName(m_cms, usedContainer); 539 540 // initialize a string mapper to resolve EL like strings in tag attributes 541 CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(m_cms).setJspPageContext( 542 pageContext).setResourceName(resourcename).setKeepEmptyMacros(true); 543 544 // resolve the collector name 545 if (container == null) { 546 // no preload parent container, initialize new values 547 m_collectorName = resolver.resolveMacros(getCollector()); 548 // resolve the parameter 549 m_collectorParam = resolver.resolveMacros(getParam()); 550 m_collectorResult = null; 551 } else { 552 // preload parent content container available, use values from this container 553 m_collectorName = usedContainer.getCollectorName(); 554 m_collectorParam = usedContainer.getCollectorParam(); 555 m_collectorResult = usedContainer.getCollectorResult(); 556 if (m_locale == null) { 557 // use locale from ancestor if available 558 m_locale = usedContainer.getXmlDocumentLocale(); 559 } 560 } 561 562 if (m_locale == null) { 563 // no locale set, use locale from users request context 564 m_locale = m_cms.getRequestContext().getLocale(); 565 } 566 567 try { 568 // now collect the resources 569 I_CmsResourceCollector collector = OpenCms.getResourceManager().getContentCollector(m_collectorName); 570 if (collector == null) { 571 throw new CmsException(Messages.get().container(Messages.ERR_COLLECTOR_NOT_FOUND_1, m_collectorName)); 572 } 573 // execute the collector if not already done in parent tag 574 if (m_collectorResult == null) { 575 m_collectorResult = collector.getResults(m_cms, m_collectorName, m_collectorParam); 576 } 577 578 m_contentInfoBean = new CmsContentInfoBean(); 579 m_contentInfoBean.setPageSizeAsString(resolver.resolveMacros(m_pageSize)); 580 m_contentInfoBean.setPageIndexAsString(resolver.resolveMacros(m_pageIndex)); 581 m_contentInfoBean.setPageNavLengthAsString(resolver.resolveMacros(m_pageNavLength)); 582 m_contentInfoBean.setResultSize(m_collectorResult.size()); 583 m_contentInfoBean.setLocale(m_locale.toString()); 584 m_contentInfoBean.initResultIndex(); 585 586 if (!isPreloader()) { 587 // not required when only preloading 588 m_collectorResult = CmsJspTagResourceLoad.limitCollectorResult(m_contentInfoBean, m_collectorResult); 589 m_contentInfoBean.initPageNavIndexes(); 590 591 String createParam = collector.getCreateParam(m_cms, m_collectorName, m_collectorParam); 592 if ((createParam != null) && CmsDefaultFileNameGenerator.hasNumberMacro(createParam)) { 593 // use "create link" only if collector supports it and it contains the number macro for new file names 594 m_directEditLinkForNew = CmsEncoder.encode(m_collectorName + "|" + createParam); 595 } 596 } else if (isScopeVarSet()) { 597 // scope variable is set, store content load bean in JSP context 598 CmsJspContentLoadBean bean = new CmsJspContentLoadBean(m_cms, m_locale, m_collectorResult); 599 storeAttribute(bean); 600 } 601 602 } catch (CmsException e) { 603 m_controller.setThrowable(e, m_cms.getRequestContext().getUri()); 604 throw new JspException(e); 605 } 606 607 // reset the direct edit options (required because of re-used tags) 608 m_directEditOpen = false; 609 m_directEditFollowButtons = null; 610 611 // the next loop is the first loop 612 m_isFirstLoop = true; 613 } 614}