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.ade.publish; 029 030import org.opencms.ade.publish.CmsPublishRelationFinder.ResourceMap; 031import org.opencms.ade.publish.shared.CmsProjectBean; 032import org.opencms.ade.publish.shared.CmsPublishData; 033import org.opencms.ade.publish.shared.CmsPublishGroup; 034import org.opencms.ade.publish.shared.CmsPublishGroupList; 035import org.opencms.ade.publish.shared.CmsPublishListToken; 036import org.opencms.ade.publish.shared.CmsPublishOptions; 037import org.opencms.ade.publish.shared.CmsPublishResource; 038import org.opencms.ade.publish.shared.CmsWorkflow; 039import org.opencms.ade.publish.shared.CmsWorkflowAction; 040import org.opencms.ade.publish.shared.CmsWorkflowActionParams; 041import org.opencms.ade.publish.shared.CmsWorkflowResponse; 042import org.opencms.ade.publish.shared.rpc.I_CmsPublishService; 043import org.opencms.db.CmsUserSettings; 044import org.opencms.file.CmsObject; 045import org.opencms.file.CmsResource; 046import org.opencms.file.CmsResourceFilter; 047import org.opencms.file.CmsUser; 048import org.opencms.flex.CmsFlexController; 049import org.opencms.gwt.CmsGwtService; 050import org.opencms.gwt.CmsRpcException; 051import org.opencms.main.CmsException; 052import org.opencms.main.CmsLog; 053import org.opencms.main.OpenCms; 054import org.opencms.ui.CmsVaadinUtils; 055import org.opencms.util.CmsStringUtil; 056import org.opencms.util.CmsUUID; 057import org.opencms.workflow.CmsWorkflowResources; 058import org.opencms.workflow.I_CmsPublishResourceFormatter; 059import org.opencms.workflow.I_CmsWorkflowManager; 060import org.opencms.workplace.CmsDialog; 061import org.opencms.workplace.CmsWorkplace; 062 063import java.util.ArrayList; 064import java.util.HashMap; 065import java.util.HashSet; 066import java.util.List; 067import java.util.Locale; 068import java.util.Map; 069import java.util.Set; 070import java.util.stream.Collectors; 071 072import javax.servlet.http.HttpServletRequest; 073 074import org.apache.commons.logging.Log; 075 076import com.google.common.collect.Lists; 077import com.google.common.collect.Maps; 078import com.google.common.collect.Sets; 079 080/** 081 * The implementation of the publish service. <p> 082 * 083 * @since 8.0.0 084 * 085 */ 086public class CmsPublishService extends CmsGwtService implements I_CmsPublishService { 087 088 /** Name for the request parameter to control display of the confirmation dialog. */ 089 public static final String PARAM_CONFIRM = "confirm"; 090 091 /** The publish project id parameter name. */ 092 public static final String PARAM_PUBLISH_PROJECT_ID = "publishProjectId"; 093 094 /** The workflow id parameter name. */ 095 public static final String PARAM_WORKFLOW_ID = "workflowId"; 096 097 /** The logger for this class. */ 098 private static final Log LOG = CmsLog.getLog(CmsPublishService.class); 099 100 /** The version id for serialization. */ 101 private static final long serialVersionUID = 3852074177607037076L; 102 103 /** Session attribute name constant. */ 104 private static final String SESSION_ATTR_ADE_PUB_OPTS_CACHE = "__OCMS_ADE_PUB_OPTS_CACHE__"; 105 106 /** 107 * Fetches the publish data.<p> 108 * 109 * @param request the servlet request 110 * 111 * @return the publish data 112 * 113 * @throws CmsRpcException if something goes wrong 114 */ 115 public static CmsPublishData prefetch(HttpServletRequest request) throws CmsRpcException { 116 117 CmsPublishService srv = new CmsPublishService(); 118 srv.setCms(CmsFlexController.getCmsObject(request)); 119 srv.setRequest(request); 120 CmsPublishData result = null; 121 HashMap<String, String> params = Maps.newHashMap(); 122 params.put("prefetch", "true"); 123 try { 124 result = srv.getInitData(params); 125 } finally { 126 srv.clearThreadStorage(); 127 } 128 return result; 129 } 130 131 /** 132 * Wraps the project name in a message string.<p> 133 * 134 * @param cms the CMS context 135 * @param name the project name 136 * 137 * @return the message for the given project name 138 */ 139 public static String wrapProjectName(CmsObject cms, String name) { 140 141 return Messages.get().getBundle(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms)).key( 142 Messages.GUI_NORMAL_PROJECT_1, 143 name); 144 } 145 146 /** 147 * @see org.opencms.ade.publish.shared.rpc.I_CmsPublishService#executeAction(org.opencms.ade.publish.shared.CmsWorkflowAction, org.opencms.ade.publish.shared.CmsWorkflowActionParams) 148 */ 149 public CmsWorkflowResponse executeAction(CmsWorkflowAction action, CmsWorkflowActionParams params) 150 throws CmsRpcException { 151 152 CmsWorkflowResponse response = null; 153 try { 154 CmsObject cms = getCmsObject(); 155 if (params.getToken() == null) { 156 CmsPublishOptions options = getCachedOptions(); 157 CmsPublish pub = new CmsPublish(cms, options); 158 List<CmsResource> publishResources = idsToResources(cms, params.getPublishIds()); 159 Set<CmsUUID> toRemove = new HashSet<CmsUUID>(params.getRemoveIds()); 160 pub.removeResourcesFromPublishList(toRemove); 161 response = OpenCms.getWorkflowManager().executeAction(cms, action, options, publishResources); 162 } else { 163 response = OpenCms.getWorkflowManager().executeAction(cms, action, params.getToken()); 164 } 165 } catch (Throwable e) { 166 error(e); 167 } 168 return response; 169 } 170 171 /** 172 * @see org.opencms.ade.publish.shared.rpc.I_CmsPublishService#getInitData(java.util.HashMap) 173 */ 174 public CmsPublishData getInitData(HashMap<String, String> params) throws CmsRpcException { 175 176 CmsPublishData result = null; 177 CmsObject cms = getCmsObject(); 178 String closeLink = getRequest().getParameter(CmsDialog.PARAM_CLOSELINK); 179 if ((closeLink == null) && "true".equals(params.get("prefetch"))) { 180 closeLink = CmsVaadinUtils.getWorkplaceLink(); 181 } 182 String confirmStr = getRequest().getParameter(PARAM_CONFIRM); 183 boolean confirm = Boolean.parseBoolean(confirmStr); 184 String workflowId = getRequest().getParameter(PARAM_WORKFLOW_ID); 185 String projectParam = getRequest().getParameter(PARAM_PUBLISH_PROJECT_ID); 186 String filesParam = getRequest().getParameter(CmsWorkplace.PARAM_RESOURCELIST); 187 if (CmsStringUtil.isEmptyOrWhitespaceOnly(filesParam)) { 188 filesParam = getRequest().getParameter(CmsDialog.PARAM_RESOURCE); 189 } 190 List<String> pathList = Lists.newArrayList(); 191 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(filesParam)) { 192 pathList = CmsStringUtil.splitAsList(filesParam, "|"); 193 } 194 try { 195 result = getPublishData(cms, params, workflowId, projectParam, pathList, closeLink, confirm); 196 } catch (Throwable e) { 197 error(e); 198 } 199 return result; 200 } 201 202 /** 203 * Gets the publish data for the given parameters.<p> 204 * 205 * @param cms the CMS context 206 * @param params other publish parameters 207 * @param workflowId the workflow id 208 * @param projectParam the project 209 * @param pathList the list of direct publish resource site paths 210 * @param closeLink the close link 211 * @param confirm true if confirmation dialog should be displayed after closing the dialog 212 * 213 * @return the publish data 214 * 215 * @throws Exception if something goes wrong 216 */ 217 public CmsPublishData getPublishData( 218 CmsObject cms, 219 HashMap<String, String> params, 220 String workflowId, 221 String projectParam, 222 List<String> pathList, 223 String closeLink, 224 boolean confirm) 225 throws Exception { 226 227 CmsPublishData result; 228 boolean canOverrideWorkflow = true; 229 Map<String, CmsWorkflow> workflows = OpenCms.getWorkflowManager().getWorkflows(cms); 230 if (workflows.isEmpty()) { 231 throw new Exception("No workflow available for the current user"); 232 } 233 if (CmsStringUtil.isEmptyOrWhitespaceOnly(workflowId) || !workflows.containsKey(workflowId)) { 234 workflowId = getLastWorkflowForUser(); 235 if (CmsStringUtil.isEmptyOrWhitespaceOnly(workflowId) || !workflows.containsKey(workflowId)) { 236 workflowId = workflows.values().iterator().next().getId(); 237 } 238 } else { 239 canOverrideWorkflow = false; 240 } 241 setLastWorkflowForUser(workflowId); 242 243 // need to put this into params here so that the virtual project for direct publishing is included in the result of getManageableProjects() 244 if (!pathList.isEmpty()) { 245 params.put(CmsPublishOptions.PARAM_FILES, CmsStringUtil.listAsString(pathList, "|")); 246 } 247 248 boolean useCurrentPageAsDefault = params.containsKey(CmsPublishOptions.PARAM_START_WITH_CURRENT_PAGE); 249 CmsPublishOptions options = getCachedOptions(); 250 List<CmsProjectBean> projects = OpenCms.getWorkflowManager().getManageableProjects(cms, params); 251 Set<CmsUUID> availableProjectIds = Sets.newHashSet(); 252 for (CmsProjectBean projectBean : projects) { 253 availableProjectIds.add(projectBean.getId()); 254 } 255 CmsUUID defaultProjectId = CmsUUID.getNullUUID(); 256 if (useCurrentPageAsDefault && availableProjectIds.contains(CmsCurrentPageProject.ID)) { 257 defaultProjectId = CmsCurrentPageProject.ID; 258 } 259 260 boolean foundProject = false; 261 CmsUUID selectedProject = null; 262 if (!pathList.isEmpty()) { 263 int enableIncludeContents = 0; 264 if (pathList.size() > 1) { 265 enableIncludeContents = 2; 266 } else { 267 try { 268 if (cms.readResource(pathList.get(0), CmsResourceFilter.ALL).isFolder()) { 269 enableIncludeContents = 1; 270 } 271 } catch (CmsException e) { 272 LOG.info(e.getLocalizedMessage(), e); 273 } 274 } 275 params.put(CmsPublishOptions.PARAM_ENABLE_INCLUDE_CONTENTS, "" + enableIncludeContents); 276 params.put(CmsPublishOptions.PARAM_INCLUDE_CONTENTS, Boolean.TRUE.toString()); 277 selectedProject = CmsDirectPublishProject.ID; 278 foundProject = true; 279 } else { 280 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(projectParam) && CmsUUID.isValidUUID(projectParam)) { 281 selectedProject = new CmsUUID(projectParam); 282 // check if the selected project is a manageable project 283 for (CmsProjectBean project : projects) { 284 if (selectedProject.equals(project.getId())) { 285 foundProject = true; 286 if (project.isWorkflowProject()) { 287 canOverrideWorkflow = false; 288 workflowId = OpenCms.getWorkflowManager().getWorkflowForWorkflowProject(selectedProject); 289 } 290 break; 291 } 292 } 293 } 294 if (!foundProject) { 295 selectedProject = options.getProjectId(); 296 if (selectedProject == null) { 297 selectedProject = defaultProjectId; 298 foundProject = true; 299 } else { 300 // check if the selected project is a manageable project 301 for (CmsProjectBean project : projects) { 302 if (selectedProject.equals(project.getId())) { 303 foundProject = true; 304 if (project.isWorkflowProject()) { 305 canOverrideWorkflow = false; 306 workflowId = OpenCms.getWorkflowManager().getWorkflowForWorkflowProject( 307 selectedProject); 308 } 309 break; 310 } 311 } 312 } 313 } 314 } 315 if (foundProject) { 316 options.setProjectId(selectedProject); 317 } else { 318 options.setProjectId(CmsUUID.getNullUUID()); 319 } 320 321 options.setParameters(params); 322 result = new CmsPublishData( 323 options, 324 projects, 325 getResourceGroups(workflows.get(workflowId), options, canOverrideWorkflow), 326 workflows, 327 workflowId); 328 result.setCloseLink(closeLink); 329 result.setShowConfirmation(confirm); 330 return result; 331 } 332 333 /** 334 * @see org.opencms.ade.publish.shared.rpc.I_CmsPublishService#getResourceGroups(org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions, boolean) 335 */ 336 public CmsPublishGroupList getResourceGroups( 337 CmsWorkflow workflow, 338 CmsPublishOptions options, 339 boolean projectChanged) 340 throws CmsRpcException { 341 342 List<CmsPublishGroup> results = null; 343 CmsObject cms = getCmsObject(); 344 String overrideWorkflowId = null; 345 try { 346 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 347 I_CmsWorkflowManager workflowManager = OpenCms.getWorkflowManager(); 348 I_CmsPublishResourceFormatter formatter = workflowManager.createFormatter(cms, workflow, options); 349 CmsWorkflowResources workflowResources = null; 350 workflowResources = workflowManager.getWorkflowResources(cms, workflow, options, projectChanged, false); 351 if (workflowResources.getOverrideWorkflow() != null) { 352 overrideWorkflowId = workflowResources.getOverrideWorkflow().getId(); 353 formatter = workflowManager.createFormatter(cms, workflowResources.getOverrideWorkflow(), options); 354 } 355 if (workflowResources.isTooMany()) { 356 // too many resources, send a publish list token to the client which can be used later to restore the resource list 357 CmsPublishListToken token = workflowManager.getPublishListToken(cms, workflow, options); 358 CmsPublishGroupList result = new CmsPublishGroupList(token); 359 result.setTooManyResourcesMessage( 360 Messages.get().getBundle(locale).key( 361 Messages.GUI_TOO_MANY_RESOURCES_2, 362 "" + workflowResources.getLowerBoundForSize(), 363 "" + OpenCms.getWorkflowManager().getResourceLimit())); 364 return result; 365 } 366 I_CmsVirtualProject virtualProject = workflowManager.getRealOrVirtualProject(options.getProjectId()); 367 I_CmsPublishRelatedResourceProvider relProvider = virtualProject.getRelatedResourceProvider( 368 getCmsObject(), 369 options); 370 ResourceMap resourcesAndRelated = getResourcesAndRelated( 371 workflowResources.getWorkflowResources(), 372 options.isIncludeRelated(), 373 options.isIncludeSiblings(), 374 (options.getProjectId() == null) || options.getProjectId().isNullUUID(), 375 relProvider); 376 formatter.initialize(options, resourcesAndRelated); 377 List<CmsPublishResource> publishResources = formatter.getPublishResources(); 378 A_CmsPublishGroupHelper<CmsPublishResource, CmsPublishGroup> groupHelper; 379 boolean autoSelectable = true; 380 if ((options.getProjectId() == null) || options.getProjectId().isNullUUID()) { 381 groupHelper = new CmsDefaultPublishGroupHelper(locale); 382 } else { 383 String title = ""; 384 CmsProjectBean projectBean = virtualProject.getProjectBean(cms, options.getParameters()); 385 title = projectBean.getDefaultGroupName(); 386 if (title == null) { 387 title = ""; 388 } 389 autoSelectable = virtualProject.isAutoSelectable(); 390 groupHelper = new CmsSinglePublishGroupHelper(locale, title); 391 } 392 results = groupHelper.getGroups(publishResources); 393 for (CmsPublishGroup group : results) { 394 group.setAutoSelectable(autoSelectable); 395 } 396 setCachedOptions(options); 397 setLastWorkflowForUser(workflow.getId()); 398 } catch (Throwable e) { 399 error(e); 400 } 401 CmsPublishGroupList wrapper = new CmsPublishGroupList(); 402 wrapper.setOverrideWorkflowId(overrideWorkflowId); 403 wrapper.setGroups(results); 404 return wrapper; 405 } 406 407 /** 408 * Retrieves the publish options.<p> 409 * 410 * @return the publish options last used 411 * 412 * @throws CmsRpcException if something goes wrong 413 */ 414 public CmsPublishOptions getResourceOptions() throws CmsRpcException { 415 416 CmsPublishOptions result = null; 417 try { 418 result = getCachedOptions(); 419 } catch (Throwable e) { 420 error(e); 421 } 422 return result; 423 } 424 425 /** 426 * Adds siblings to a set of publish resources.<p> 427 * 428 * @param publishResources the set to which siblings should be added 429 */ 430 protected void addSiblings(Set<CmsResource> publishResources) { 431 432 List<CmsResource> toAdd = Lists.newArrayList(); 433 for (CmsResource resource : publishResources) { 434 if (!resource.getState().isUnchanged()) { 435 try { 436 List<CmsResource> changedSiblings = getCmsObject().readSiblings( 437 resource, 438 CmsResourceFilter.ALL_MODIFIED); 439 toAdd.addAll(changedSiblings); 440 } catch (CmsException e) { 441 LOG.error(e.getLocalizedMessage(), e); 442 } 443 } 444 } 445 publishResources.addAll(toAdd); 446 } 447 448 /** 449 * Returns the cached publish options, creating it if it doesn't already exist.<p> 450 * 451 * @return the cached publish options 452 */ 453 private CmsPublishOptions getCachedOptions() { 454 455 CmsPublishOptions cache = (CmsPublishOptions)getRequest().getSession().getAttribute( 456 SESSION_ATTR_ADE_PUB_OPTS_CACHE); 457 if (cache == null) { 458 CmsUserSettings settings = new CmsUserSettings(getCmsObject()); 459 cache = new CmsPublishOptions(); 460 cache.setIncludeSiblings(settings.getDialogPublishSiblings()); 461 getRequest().getSession().setAttribute(SESSION_ATTR_ADE_PUB_OPTS_CACHE, cache); 462 } 463 return cache; 464 465 } 466 467 /** 468 * Returns the id of the last used workflow for the current user.<p> 469 * 470 * @return the workflow id 471 */ 472 private String getLastWorkflowForUser() { 473 474 CmsUser user = getCmsObject().getRequestContext().getCurrentUser(); 475 return (String)user.getAdditionalInfo(PARAM_WORKFLOW_ID); 476 } 477 478 /** 479 * Gets the resource map containing the publish resources together with their related resources.<p> 480 * 481 * @param resources the base list of publish resources 482 * @param includeRelated flag to control whether related resources should be included 483 * @param includeSiblings flag to control whether siblings should be included 484 * @param keepOriginalUnchangedResources flag which determines whether unchanged resources in the original resource list should be kept or removed 485 * @param relProvider the provider for additional related resources 486 * 487 * @return the resources together with their related resources 488 */ 489 private ResourceMap getResourcesAndRelated( 490 List<CmsResource> resources, 491 boolean includeRelated, 492 boolean includeSiblings, 493 boolean keepOriginalUnchangedResources, 494 I_CmsPublishRelatedResourceProvider relProvider) { 495 496 Set<CmsResource> publishResources = new HashSet<CmsResource>(); 497 publishResources.addAll(resources); 498 if (includeSiblings) { 499 addSiblings(publishResources); 500 } 501 Set<CmsResource> visibleResources = publishResources.stream().filter( 502 resource -> getCmsObject().existsResource( 503 resource.getStructureId(), 504 CmsResourceFilter.ALL.addRequireVisible()) 505 506 ).collect(Collectors.toSet()); 507 ResourceMap result; 508 if (includeRelated) { 509 CmsPublishRelationFinder relationFinder = new CmsPublishRelationFinder( 510 getCmsObject(), 511 visibleResources, 512 keepOriginalUnchangedResources, 513 relProvider); 514 515 result = relationFinder.getPublishRelatedResources(); 516 } else { 517 result = new ResourceMap(); 518 for (CmsResource resource : visibleResources) { 519 if (keepOriginalUnchangedResources || !resource.getState().isUnchanged()) { 520 result.put(resource, new HashSet<CmsResource>()); 521 } 522 } 523 } 524 return result; 525 } 526 527 /** 528 * Converts a list of IDs to resources.<p> 529 * 530 * @param cms the CmObject used for reading the resources 531 * @param ids the list of IDs 532 * 533 * @return a list of resources 534 */ 535 private List<CmsResource> idsToResources(CmsObject cms, List<CmsUUID> ids) { 536 537 List<CmsResource> result = new ArrayList<CmsResource>(); 538 for (CmsUUID id : ids) { 539 try { 540 CmsResource resource = cms.readResource(id, CmsResourceFilter.ALL); 541 result.add(resource); 542 } catch (CmsException e) { 543 // should never happen 544 logError(e); 545 } 546 } 547 return result; 548 } 549 550 /** 551 * Saves the given options to the session.<p> 552 * 553 * @param options the options to save 554 */ 555 private void setCachedOptions(CmsPublishOptions options) { 556 557 getRequest().getSession().setAttribute(SESSION_ATTR_ADE_PUB_OPTS_CACHE, options); 558 } 559 560 /** 561 * Writes the id of the last used workflow to the current user.<p> 562 * 563 * @param workflowId the workflow id 564 * 565 * @throws CmsException if something goes wrong writing the user object 566 */ 567 private void setLastWorkflowForUser(String workflowId) throws CmsException { 568 569 CmsUser user = getCmsObject().getRequestContext().getCurrentUser(); 570 user.setAdditionalInfo(PARAM_WORKFLOW_ID, workflowId); 571 getCmsObject().writeUser(user); 572 } 573}