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