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, 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.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsResourceTypeConfig; 032import org.opencms.ade.contenteditor.shared.CmsEditorConstants; 033import org.opencms.file.CmsFile; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.collectors.A_CmsResourceCollector; 038import org.opencms.file.collectors.I_CmsCollectorPostCreateHandler; 039import org.opencms.file.types.I_CmsResourceType; 040import org.opencms.flex.CmsFlexController; 041import org.opencms.loader.CmsLoaderException; 042import org.opencms.main.CmsException; 043import org.opencms.main.CmsIllegalArgumentException; 044import org.opencms.main.CmsLog; 045import org.opencms.main.OpenCms; 046import org.opencms.util.CmsPair; 047import org.opencms.util.CmsStringUtil; 048import org.opencms.util.CmsUUID; 049import org.opencms.workplace.editors.directedit.CmsDirectEditButtonSelection; 050import org.opencms.workplace.editors.directedit.CmsDirectEditParams; 051 052import java.util.Locale; 053 054import javax.servlet.jsp.JspException; 055import javax.servlet.jsp.PageContext; 056 057import org.apache.commons.lang3.StringUtils; 058import org.apache.commons.logging.Log; 059 060/** This tag is used to attach an edit provider to a snippet of HTML. */ 061public class CmsJspTagEdit extends CmsJspScopedVarBodyTagSuport { 062 063 /** Identifier to indicate that the new link should be handled by this tag - not by a {@link org.opencms.file.collectors.I_CmsResourceCollector}. */ 064 public static final String NEW_LINK_IDENTIFIER = "__edit__"; 065 066 /** The log object for this class. */ 067 private static final Log LOG = CmsLog.getLog(CmsJspTagEdit.class); 068 069 /** Serial version UID required for safe serialization. */ 070 private static final long serialVersionUID = -3781368910893187306L; 071 072 /** Flag, indicating if the create option should be displayed. */ 073 private boolean m_canCreate; 074 075 /** Flag, indicating if the delete option should be displayed. */ 076 private boolean m_canDelete; 077 078 /** The type of the resource that should be created. */ 079 private String m_createType; 080 081 /** The tag attribute's value, specifying the path to the (sub)sitemap where new content should be created. */ 082 private String m_creationSiteMap; 083 084 /** Flag, indicating if during rendering the "startDirectEdit" part has been rendered, but not the "endDirectEdit" part. */ 085 private boolean m_isEditOpen; 086 087 /** The fully qualified class name of the post create handler to use. */ 088 private String m_postCreateHandler; 089 090 /** UUID of the content to edit. */ 091 private String m_uuid; 092 093 /** Creates a new resource. 094 * @param cmsObject The CmsObject of the current request context. 095 * @param newLink A string, specifying where which new content should be created. 096 * @param locale The locale for which the 097 * @param sitePath site path of the currently edited content. 098 * @param modelFileName not used. 099 * @param mode optional creation mode 100 * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created. 101 * The fully qualified class name can be followed by a "|" symbol and a handler specific configuration string. 102 * @return The site-path of the newly created resource. 103 */ 104 public static String createResource( 105 CmsObject cmsObject, 106 String newLink, 107 Locale locale, 108 String sitePath, 109 String modelFileName, 110 String mode, 111 String postCreateHandler) { 112 113 String[] newLinkParts = newLink.split("\\|"); 114 String rootPath = newLinkParts[1]; 115 String typeName = newLinkParts[2]; 116 CmsFile modelFile = null; 117 if (StringUtils.equalsIgnoreCase(mode, CmsEditorConstants.MODE_COPY)) { 118 try { 119 modelFile = cmsObject.readFile(sitePath); 120 } catch (CmsException e) { 121 LOG.warn( 122 "The resource at path" + sitePath + "could not be read. Thus it can not be used as model file.", 123 e); 124 } 125 } 126 CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfiguration(cmsObject, rootPath); 127 CmsResourceTypeConfig typeConfig = adeConfig.getResourceType(typeName); 128 CmsResource newElement = null; 129 try { 130 CmsObject cmsClone = cmsObject; 131 if ((locale != null) && !cmsObject.getRequestContext().getLocale().equals(locale)) { 132 // in case the content locale does not match the request context locale, use a clone cms with the appropriate locale 133 cmsClone = OpenCms.initCmsObject(cmsObject); 134 cmsClone.getRequestContext().setLocale(locale); 135 } 136 newElement = typeConfig.createNewElement(cmsClone, modelFile, rootPath); 137 CmsPair<String, String> handlerParameter = I_CmsCollectorPostCreateHandler.splitClassAndConfig( 138 postCreateHandler); 139 I_CmsCollectorPostCreateHandler handler = A_CmsResourceCollector.getPostCreateHandler( 140 handlerParameter.getFirst()); 141 handler.onCreate(cmsClone, cmsClone.readFile(newElement), modelFile != null, handlerParameter.getSecond()); 142 } catch (CmsException e) { 143 LOG.error("Could not create resource.", e); 144 } 145 return newElement == null ? null : cmsObject.getSitePath(newElement); 146 } 147 148 /** 149 * Creates the String specifying where which type of resource has to be created.<p> 150 * 151 * @param cms the CMS context 152 * @param resType the resource type to create 153 * @param creationSitemap the creation sitemap parameter 154 * 155 * @return The String identifying which type of resource has to be created where.<p> 156 * 157 * @see #createResource(CmsObject, String, Locale, String, String, String, String) 158 */ 159 public static String getNewLink(CmsObject cms, I_CmsResourceType resType, String creationSitemap) { 160 161 String contextPath = getContextRootPath(cms, creationSitemap); 162 StringBuffer newLink = new StringBuffer(NEW_LINK_IDENTIFIER); 163 newLink.append('|'); 164 newLink.append(contextPath); 165 newLink.append('|'); 166 newLink.append(resType.getTypeName()); 167 168 return newLink.toString(); 169 } 170 171 /** 172 * Returns the resource type name contained in the newLink parameter.<p> 173 * 174 * @param newLink the newLink parameter 175 * 176 * @return the resource type name 177 */ 178 public static String getRootPathFromNewLink(String newLink) { 179 180 String result = null; 181 if (newLink.startsWith(NEW_LINK_IDENTIFIER) && newLink.contains("|")) { 182 result = newLink.substring(newLink.indexOf("|") + 1, newLink.lastIndexOf("|")); 183 } 184 return result; 185 } 186 187 /** 188 * Returns the resource type name contained in the newLink parameter.<p> 189 * 190 * @param newLink the newLink parameter 191 * 192 * @return the resource type name 193 */ 194 public static String getTypeFromNewLink(String newLink) { 195 196 String result = null; 197 if (newLink.startsWith(NEW_LINK_IDENTIFIER) && newLink.contains("|")) { 198 result = newLink.substring(newLink.lastIndexOf("|") + 1); 199 } 200 201 return result; 202 } 203 204 /** 205 * Inserts the closing direct edit tag.<p> 206 * 207 * @param pageContext the page context 208 */ 209 public static void insertDirectEditEnd(PageContext pageContext) { 210 211 try { 212 CmsJspTagEditable.endDirectEdit(pageContext); 213 } catch (JspException e) { 214 LOG.error("Could not print closing direct edit tag.", e); 215 } 216 } 217 218 /** 219 * Inserts the opening direct edit tag.<p> 220 * 221 * @param cms the CMS context 222 * @param pageContext the page context 223 * @param resource the resource to edit 224 * @param canCreate if resource creation is allowed 225 * @param canDelete if resource deletion is allowed 226 * @param createType the resource type to create, default to the type of the edited resource 227 * @param creationSitemap the sitemap context to create the resource in, default to the current requested URI 228 * @param postCreateHandler the post create handler if required 229 * 230 * @return <code>true</code> if an opening direct edit tag was inserted 231 */ 232 public static boolean insertDirectEditStart( 233 CmsObject cms, 234 PageContext pageContext, 235 CmsResource resource, 236 boolean canCreate, 237 boolean canDelete, 238 String createType, 239 String creationSitemap, 240 String postCreateHandler) { 241 242 boolean result = false; 243 CmsDirectEditParams editParams = null; 244 if (resource != null) { 245 246 String newLink = null; 247 // reconstruct create type from the edit-resource if necessary 248 if (canCreate) { 249 I_CmsResourceType resType = getResourceType(resource, createType); 250 if (resType != null) { 251 newLink = getNewLink(cms, resType, creationSitemap); 252 } 253 } 254 CmsDirectEditButtonSelection buttons = null; 255 if (canDelete) { 256 if (newLink != null) { 257 buttons = CmsDirectEditButtonSelection.EDIT_DELETE_NEW; 258 } else { 259 buttons = CmsDirectEditButtonSelection.EDIT_DELETE; 260 } 261 } else if (newLink != null) { 262 buttons = CmsDirectEditButtonSelection.EDIT_NEW; 263 } else { 264 buttons = CmsDirectEditButtonSelection.EDIT; 265 } 266 editParams = new CmsDirectEditParams(cms.getSitePath(resource), buttons, null, newLink); 267 } else if (canCreate) { 268 I_CmsResourceType resType = getResourceType(null, createType); 269 if (resType != null) { 270 editParams = new CmsDirectEditParams( 271 cms.getRequestContext().getFolderUri(), 272 CmsDirectEditButtonSelection.NEW, 273 null, 274 getNewLink(cms, resType, creationSitemap)); 275 } 276 } 277 278 if (editParams != null) { 279 editParams.setPostCreateHandler(postCreateHandler); 280 try { 281 CmsJspTagEditable.startDirectEdit(pageContext, editParams); 282 result = true; 283 } catch (JspException e) { 284 // TODO: Localize and improve error message. 285 LOG.error("Could not create direct edit start.", e); 286 } 287 } 288 return result; 289 } 290 291 /** 292 * Returns the context root path.<p> 293 * 294 * @param cms the CMS context 295 * @param creationSitemap the creation sitemap parameter 296 * 297 * @return the context root path 298 */ 299 private static String getContextRootPath(CmsObject cms, String creationSitemap) { 300 301 String path = null; 302 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(creationSitemap)) { 303 try { 304 path = cms.readFolder(creationSitemap).getRootPath(); 305 } catch (CmsException e) { 306 LOG.warn("The provided creation sitemap " + creationSitemap + " is not a VFS folder.", e); 307 } 308 } 309 if (path == null) { 310 path = cms.addSiteRoot(cms.getRequestContext().getFolderUri()); 311 } 312 313 return path; 314 } 315 316 /** 317 * Returns the resource type to create, or <code>null</code> if not available.<p> 318 * 319 * @param resource the edit resource 320 * @param createType the create type parameter 321 * 322 * @return the resource type 323 */ 324 private static I_CmsResourceType getResourceType(CmsResource resource, String createType) { 325 326 I_CmsResourceType resType = null; 327 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(createType)) { 328 try { 329 resType = OpenCms.getResourceManager().getResourceType(createType); 330 } catch (CmsLoaderException e) { 331 LOG.error("Could not read resource type '" + createType + "' for resource creation.", e); 332 } 333 } else if (resource != null) { 334 resType = OpenCms.getResourceManager().getResourceType(resource); 335 } 336 return resType; 337 } 338 339 /** 340 * @see javax.servlet.jsp.tagext.BodyTagSupport#doEndTag() 341 */ 342 @Override 343 public int doEndTag() throws JspException { 344 345 if (m_isEditOpen) { 346 CmsJspTagEditable.endDirectEdit(pageContext); 347 } 348 release(); 349 return EVAL_PAGE; 350 } 351 352 /** 353 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 354 */ 355 @Override 356 public int doStartTag() throws CmsIllegalArgumentException { 357 358 CmsObject cms = getCmsObject(); 359 m_isEditOpen = insertDirectEditStart( 360 cms, 361 pageContext, 362 getResourceToEdit(cms), 363 m_canCreate || (null != m_createType), 364 m_canDelete, 365 m_createType, 366 m_creationSiteMap, 367 m_postCreateHandler); 368 return EVAL_BODY_INCLUDE; 369 } 370 371 /** 372 * @see org.opencms.jsp.CmsJspScopedVarBodyTagSuport#release() 373 */ 374 @Override 375 public void release() { 376 377 m_canCreate = false; 378 m_canDelete = false; 379 m_creationSiteMap = null; 380 m_createType = null; 381 m_isEditOpen = false; 382 m_uuid = null; 383 super.release(); 384 } 385 386 /** Setter for the "create" attribute of the tag. 387 * @param canCreate value of the tag's attribute "create". 388 */ 389 public void setCreate(final Boolean canCreate) { 390 391 m_canCreate = canCreate == null ? false : canCreate.booleanValue(); 392 } 393 394 /** Setter for the "createType" attribute of the tag.<p> 395 * 396 * @param typeName value of the "createType" attribute of the tag. 397 */ 398 public void setCreateType(final String typeName) { 399 400 m_createType = typeName; 401 } 402 403 /** Setter for the "creationSiteMap" attribute of the tag. 404 * 405 * @param sitePath value of the "creationSiteMap" attribute of the tag. 406 */ 407 public void setCreationSiteMap(final String sitePath) { 408 409 m_creationSiteMap = sitePath; 410 } 411 412 /**Setter for the "delete" attribute of the tag. 413 * @param canDelete value of the "delete" attribute of the tag. 414 */ 415 public void setDelete(final Boolean canDelete) { 416 417 m_canDelete = canDelete == null ? false : canDelete.booleanValue(); 418 } 419 420 /** Setter for the "postCreateHandler" attribute of the tag. 421 * @param postCreateHandler fully qualified class name of the {@link I_CmsCollectorPostCreateHandler} to use. 422 */ 423 public void setPostCreateHandler(final String postCreateHandler) { 424 425 m_postCreateHandler = postCreateHandler; 426 } 427 428 /** Setter for the uuid attribute of the tag, providing the uuid of content that should be edited. 429 * If no valid uuid of an existing resource is given, it is assumed the tag is only used for creating new contents. 430 * @param uuid the uuid of the content that should be edited. 431 */ 432 public void setUuid(final String uuid) { 433 434 m_uuid = uuid; 435 } 436 437 /** 438 * Returns the current CMS context.<p> 439 * 440 * @return the CMS context 441 */ 442 private CmsObject getCmsObject() { 443 444 CmsFlexController controller = CmsFlexController.getController(pageContext.getRequest()); 445 return controller.getCmsObject(); 446 447 } 448 449 /** 450 * Returns the resource to edit according to the uuid provided via the tag's attribute "uuid".<p> 451 * 452 * @param cms the CMS context 453 * 454 * @return the resource 455 */ 456 private CmsResource getResourceToEdit(CmsObject cms) { 457 458 CmsResource resource = null; 459 if (m_uuid != null) { 460 try { 461 CmsUUID uuid = new CmsUUID(m_uuid); 462 resource = cms.readResource(uuid, CmsResourceFilter.ignoreExpirationOffline(cms)); 463 464 } catch (NumberFormatException | CmsException e) { 465 LOG.warn("UUID was not valid or there is no resource with the given UUID.", e); 466 } 467 } 468 return resource; 469 } 470}