001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (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.workflow; 029 030import org.opencms.ade.publish.CmsCurrentPageProject; 031import org.opencms.ade.publish.CmsDirectPublishProject; 032import org.opencms.ade.publish.CmsMyChangesProject; 033import org.opencms.ade.publish.CmsPublish; 034import org.opencms.ade.publish.CmsRealProjectVirtualWrapper; 035import org.opencms.ade.publish.CmsTooManyPublishResourcesException; 036import org.opencms.ade.publish.I_CmsVirtualProject; 037import org.opencms.ade.publish.shared.CmsProjectBean; 038import org.opencms.ade.publish.shared.CmsPublishListToken; 039import org.opencms.ade.publish.shared.CmsPublishOptions; 040import org.opencms.ade.publish.shared.CmsPublishResource; 041import org.opencms.ade.publish.shared.CmsWorkflow; 042import org.opencms.ade.publish.shared.CmsWorkflowAction; 043import org.opencms.ade.publish.shared.CmsWorkflowResponse; 044import org.opencms.file.CmsObject; 045import org.opencms.file.CmsProject; 046import org.opencms.file.CmsResource; 047import org.opencms.i18n.CmsMessages; 048import org.opencms.main.CmsException; 049import org.opencms.main.CmsLog; 050import org.opencms.main.OpenCms; 051import org.opencms.security.CmsOrganizationalUnit; 052import org.opencms.security.CmsRole; 053import org.opencms.util.CmsUUID; 054 055import java.util.ArrayList; 056import java.util.Collections; 057import java.util.Iterator; 058import java.util.LinkedHashMap; 059import java.util.List; 060import java.util.Map; 061import java.util.concurrent.Callable; 062import java.util.concurrent.ExecutionException; 063import java.util.concurrent.FutureTask; 064import java.util.concurrent.TimeUnit; 065import java.util.concurrent.TimeoutException; 066 067import org.apache.commons.logging.Log; 068 069import com.google.common.collect.Maps; 070 071/** 072 * The default implementation of the workflow manager interface, which offers only publish functionality.<p> 073 */ 074public class CmsDefaultWorkflowManager extends A_CmsWorkflowManager { 075 076 /** The forced publish workflow action. */ 077 public static final String ACTION_FORCE_PUBLISH = "forcepublish"; 078 079 /** The publish workflow action. */ 080 public static final String ACTION_PUBLISH = "publish"; 081 082 /** Default value for the maximum number of resources in the initial publish list. */ 083 public static int DEFAULT_RESOURCE_LIMIT = 1000; 084 085 /** The parameter name for the resource limit. */ 086 public static final String PARAM_RESOURCE_LIMIT = "resourceLimit"; 087 088 /** The name for the publish action. */ 089 public static final String WORKFLOW_PUBLISH = "WORKFLOW_PUBLISH"; 090 091 /** The log instance for this class. */ 092 private static final Log LOG = CmsLog.getLog(CmsDefaultWorkflowManager.class); 093 094 /** 095 * If a request context attribute of this name is set, some internal methods used 096 * to collect lists of resources for publishing will 'give up' and throw an exception 097 * when the number of resources exceeds the resource limit of the workflow manager. 098 */ 099 public static final String ATTR_CHECK_PUBLISH_RESOURCE_LIMIT = "CHECK_PUBLISH_RESOURCE_LIMIT"; 100 101 /** The map of registered virtual projects. */ 102 protected Map<CmsUUID, I_CmsVirtualProject> m_virtualProjects = Maps.newHashMap(); 103 104 /** The number of resources in the initial publish list above which the resources are not being displayed to the user. */ 105 private int m_resourceLimit = DEFAULT_RESOURCE_LIMIT; 106 107 /** 108 * Constructor.<p> 109 */ 110 public CmsDefaultWorkflowManager() { 111 112 m_virtualProjects.put(CmsCurrentPageProject.ID, new CmsCurrentPageProject()); 113 m_virtualProjects.put(CmsMyChangesProject.ID, new CmsMyChangesProject()); 114 m_virtualProjects.put(CmsDirectPublishProject.ID, new CmsDirectPublishProject()); 115 } 116 117 /** 118 * Creates a project bean from a real project.<p> 119 * 120 * @param cms the CMS context 121 * @param project the project 122 * 123 * @return the bean containing the project information 124 */ 125 public static CmsProjectBean createProjectBeanFromProject(CmsObject cms, CmsProject project) { 126 127 CmsProjectBean manProj = new CmsProjectBean( 128 project.getUuid(), 129 project.getType().getMode(), 130 org.opencms.ade.publish.Messages.get().getBundle(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms)).key( 131 org.opencms.ade.publish.Messages.GUI_NORMAL_PROJECT_1, 132 getOuAwareName(cms, project.getName())), 133 project.getDescription()); 134 return manProj; 135 } 136 137 /** 138 * Returns the simple name if the ou is the same as the current user's ou.<p> 139 * 140 * @param cms the CMS context 141 * @param name the fully qualified name to check 142 * 143 * @return the simple name if the ou is the same as the current user's ou 144 */ 145 protected static String getOuAwareName(CmsObject cms, String name) { 146 147 String ou = CmsOrganizationalUnit.getParentFqn(name); 148 if (ou.equals(cms.getRequestContext().getCurrentUser().getOuFqn())) { 149 return CmsOrganizationalUnit.getSimpleName(name); 150 } 151 return CmsOrganizationalUnit.SEPARATOR + name; 152 } 153 154 /** 155 * @see org.opencms.workflow.I_CmsWorkflowManager#createFormatter(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions) 156 */ 157 public I_CmsPublishResourceFormatter createFormatter( 158 CmsObject cms, 159 CmsWorkflow workflow, 160 CmsPublishOptions options) { 161 162 CmsDefaultPublishResourceFormatter formatter = new CmsDefaultPublishResourceFormatter(cms); 163 return formatter; 164 } 165 166 /** 167 * @see org.opencms.workflow.I_CmsWorkflowManager#executeAction(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflowAction, org.opencms.ade.publish.shared.CmsPublishListToken) 168 */ 169 public CmsWorkflowResponse executeAction(CmsObject cms, CmsWorkflowAction action, CmsPublishListToken token) 170 throws CmsException { 171 172 if (action.getAction().equals(CmsWorkflowAction.ACTION_CANCEL)) { 173 // Don't need to get the resource list for canceling 174 return new CmsWorkflowResponse(true, action.getAction(), null, null, null); 175 } 176 List<CmsResource> resources = getWorkflowResources( 177 cms, 178 token.getWorkflow(), 179 token.getOptions(), 180 false, 181 true).getWorkflowResources(); 182 return executeAction(cms, action, token.getOptions(), resources); 183 } 184 185 /** 186 * @see org.opencms.workflow.I_CmsWorkflowManager#executeAction(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflowAction, org.opencms.ade.publish.shared.CmsPublishOptions, java.util.List) 187 */ 188 @Override 189 public CmsWorkflowResponse executeAction( 190 CmsObject userCms, 191 CmsWorkflowAction action, 192 CmsPublishOptions options, 193 List<CmsResource> resources) 194 throws CmsException { 195 196 String actionKey = action.getAction(); 197 if (CmsWorkflowAction.ACTION_CANCEL.equals(actionKey)) { 198 return new CmsWorkflowResponse(true, actionKey, null, null, null); 199 } else if (ACTION_PUBLISH.equals(actionKey)) { 200 return actionPublish(userCms, options, resources); 201 } else if (ACTION_FORCE_PUBLISH.equals(actionKey)) { 202 return actionForcePublish(userCms, options, resources); 203 } 204 throw new CmsInvalidActionException(actionKey); 205 } 206 207 /** 208 * Gets the localized label for a given CMS context and key.<p> 209 * 210 * @param cms the CMS context 211 * @param key the localization key 212 * 213 * @return the localized label 214 */ 215 public String getLabel(CmsObject cms, String key) { 216 217 CmsMessages messages = Messages.get().getBundle(getLocale(cms)); 218 return messages.key(key); 219 } 220 221 /** 222 * @see org.opencms.workflow.I_CmsWorkflowManager#getManageableProjects(org.opencms.file.CmsObject, java.util.Map) 223 */ 224 public List<CmsProjectBean> getManageableProjects(CmsObject cms, Map<String, String> params) { 225 226 List<CmsProjectBean> manProjs = new ArrayList<CmsProjectBean>(); 227 228 List<CmsProject> projects; 229 try { 230 projects = OpenCms.getOrgUnitManager().getAllManageableProjects(cms, "", true); 231 } catch (CmsException e) { 232 // should never happen 233 LOG.error(e.getLocalizedMessage(), e); 234 return manProjs; 235 } 236 237 for (CmsProject project : projects) { 238 CmsProjectBean manProj = createProjectBeanFromProject(cms, project); 239 manProjs.add(manProj); 240 } 241 242 for (I_CmsVirtualProject handler : m_virtualProjects.values()) { 243 CmsProjectBean projectBean = handler.getProjectBean(cms, params); 244 if (projectBean != null) { 245 manProjs.add(projectBean); 246 } 247 } 248 249 return manProjs; 250 } 251 252 /** 253 * @see org.opencms.workflow.I_CmsWorkflowManager#getPublishListToken(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions) 254 */ 255 public CmsPublishListToken getPublishListToken(CmsObject cms, CmsWorkflow workflow, CmsPublishOptions options) { 256 257 return new CmsPublishListToken(workflow, options); 258 } 259 260 /** 261 * @see org.opencms.workflow.I_CmsWorkflowManager#getRealOrVirtualProject(org.opencms.util.CmsUUID) 262 */ 263 public I_CmsVirtualProject getRealOrVirtualProject(CmsUUID projectId) { 264 265 I_CmsVirtualProject project = m_virtualProjects.get(projectId); 266 if (project == null) { 267 project = new CmsRealProjectVirtualWrapper(projectId); 268 } 269 return project; 270 } 271 272 /** 273 * @see org.opencms.workflow.I_CmsWorkflowManager#getResourceLimit() 274 */ 275 public int getResourceLimit() { 276 277 return m_resourceLimit; 278 } 279 280 /** 281 * @see org.opencms.workflow.I_CmsWorkflowManager#getWorkflowForWorkflowProject(org.opencms.util.CmsUUID) 282 */ 283 public String getWorkflowForWorkflowProject(CmsUUID projectId) { 284 285 return WORKFLOW_PUBLISH; 286 } 287 288 /** 289 * @see org.opencms.workflow.I_CmsWorkflowManager#getWorkflowResources(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions, boolean, boolean) 290 */ 291 @Override 292 public CmsWorkflowResources getWorkflowResources( 293 CmsObject cms, 294 CmsWorkflow workflow, 295 CmsPublishOptions options, 296 boolean canOverride, 297 boolean ignoreLimit) { 298 299 try { 300 if (!ignoreLimit) { 301 cms.getRequestContext().setAttribute( 302 CmsDefaultWorkflowManager.ATTR_CHECK_PUBLISH_RESOURCE_LIMIT, 303 Boolean.TRUE); 304 } 305 List<CmsResource> rawResourceList = new ArrayList<CmsResource>(); 306 I_CmsVirtualProject projectHandler = null; 307 projectHandler = getRealOrVirtualProject(options.getProjectId()); 308 if (projectHandler != null) { 309 rawResourceList = projectHandler.getResources(cms, options.getParameters(), workflow.getId()); 310 return new CmsWorkflowResources(rawResourceList, null, null); 311 } 312 return new CmsWorkflowResources(rawResourceList, null, null); 313 } catch (CmsTooManyPublishResourcesException e) { 314 return new CmsWorkflowResources(Collections.<CmsResource> emptyList(), null, Integer.valueOf(e.getCount())); 315 } catch (Exception e) { 316 LOG.error(e.getLocalizedMessage(), e); 317 return new CmsWorkflowResources(Collections.<CmsResource> emptyList(), null, null); 318 } finally { 319 cms.getRequestContext().removeAttribute(CmsDefaultWorkflowManager.ATTR_CHECK_PUBLISH_RESOURCE_LIMIT); 320 } 321 } 322 323 /** 324 * @see org.opencms.workflow.I_CmsWorkflowManager#getWorkflows(org.opencms.file.CmsObject) 325 */ 326 public Map<String, CmsWorkflow> getWorkflows(CmsObject cms) { 327 328 Map<String, CmsWorkflow> result = new LinkedHashMap<String, CmsWorkflow>(); 329 List<CmsWorkflowAction> actions = new ArrayList<CmsWorkflowAction>(); 330 String publishLabel = getLabel(cms, Messages.GUI_WORKFLOW_ACTION_PUBLISH_0); 331 CmsWorkflowAction publishAction = new CmsWorkflowAction(ACTION_PUBLISH, publishLabel, true, true); 332 actions.add(publishAction); 333 String workflowLabel = getLabel(cms, Messages.GUI_WORKFLOW_PUBLISH_0); 334 CmsWorkflow publishWorkflow = new CmsWorkflow(WORKFLOW_PUBLISH, workflowLabel, actions); 335 result.put(WORKFLOW_PUBLISH, publishWorkflow); 336 return result; 337 } 338 339 /** 340 * @see org.opencms.workflow.A_CmsWorkflowManager#initialize(org.opencms.file.CmsObject) 341 */ 342 @Override 343 public void initialize(CmsObject adminCms) { 344 345 super.initialize(adminCms); 346 String resourceLimitStr = getParameter(PARAM_RESOURCE_LIMIT, "invalid").trim(); 347 try { 348 m_resourceLimit = Integer.parseInt(resourceLimitStr); 349 } catch (NumberFormatException e) { 350 // ignore, resource limit will remain at the default setting 351 } 352 } 353 354 /** 355 * The implementation of the "forcepublish" workflow action.<p> 356 * 357 * @param userCms the user CMS context 358 * @param resources the resources which the action should process 359 * @param options the publish options to use 360 * @return the workflow response 361 * 362 * @throws CmsException if something goes wrong 363 */ 364 protected CmsWorkflowResponse actionForcePublish( 365 CmsObject userCms, 366 CmsPublishOptions options, 367 List<CmsResource> resources) 368 throws CmsException { 369 370 CmsPublish publish = new CmsPublish(userCms, options.getParameters()); 371 publish.publishResources(resources); 372 CmsWorkflowResponse response = new CmsWorkflowResponse( 373 true, 374 "", 375 new ArrayList<CmsPublishResource>(), 376 new ArrayList<CmsWorkflowAction>(), 377 null); 378 return response; 379 } 380 381 /** 382 * The implementation of the "publish" workflow action.<p> 383 * 384 * @param userCms the user CMS context 385 * @param options the publish options 386 * @param resources the resources which the action should process 387 * 388 * @return the workflow response 389 * @throws CmsException if something goes wrong 390 */ 391 protected CmsWorkflowResponse actionPublish( 392 CmsObject userCms, 393 CmsPublishOptions options, 394 final List<CmsResource> resources) 395 throws CmsException { 396 397 final CmsPublish publish = new CmsPublish(userCms, options); 398 // use FutureTask to get the broken links, because we can then use a different thread if it takes too long 399 final FutureTask<List<CmsPublishResource>> brokenResourcesGetter = new FutureTask<List<CmsPublishResource>>( 400 new Callable<List<CmsPublishResource>>() { 401 402 public List<CmsPublishResource> call() throws Exception { 403 404 return publish.getBrokenResources(resources); 405 } 406 }); 407 408 Thread brokenResourcesThread = new Thread(brokenResourcesGetter); 409 brokenResourcesThread.start(); 410 try { 411 List<CmsPublishResource> brokenResources = brokenResourcesGetter.get(10, TimeUnit.SECONDS); 412 if (brokenResources.size() == 0) { 413 publish.publishResources(resources); 414 CmsWorkflowResponse response = new CmsWorkflowResponse( 415 true, 416 "", 417 new ArrayList<CmsPublishResource>(), 418 new ArrayList<CmsWorkflowAction>(), 419 null); 420 return response; 421 } else { 422 String brokenResourcesLabel = getLabel(userCms, Messages.GUI_BROKEN_LINKS_0); 423 boolean canForcePublish = OpenCms.getWorkplaceManager().getDefaultUserSettings().isAllowBrokenRelations() 424 || OpenCms.getRoleManager().hasRole(userCms, CmsRole.VFS_MANAGER); 425 List<CmsWorkflowAction> actions = new ArrayList<CmsWorkflowAction>(); 426 if (canForcePublish) { 427 String forceLabel = getLabel(userCms, Messages.GUI_WORKFLOW_ACTION_FORCE_PUBLISH_0); 428 actions.add(new CmsWorkflowAction(ACTION_FORCE_PUBLISH, forceLabel, true, true)); 429 } 430 CmsWorkflowResponse response = new CmsWorkflowResponse( 431 false, 432 brokenResourcesLabel, 433 brokenResources, 434 actions, 435 null); 436 return response; 437 } 438 } catch (TimeoutException e) { 439 // Things are taking too long, do them in a different thread and just return "OK" to the client 440 Thread thread = new Thread() { 441 442 @SuppressWarnings("synthetic-access") 443 @Override 444 public void run() { 445 446 LOG.info( 447 "Checking broken relations is taking too long, using a different thread for checking and publishing now."); 448 try { 449 // Make sure the computation is finished by calling get() without a timeout parameter 450 // We don't need the actual result of the get(), though; we just get the set of resource paths from the validator object 451 brokenResourcesGetter.get(); 452 List<CmsResource> resourcesToPublish = new ArrayList<CmsResource>(resources); 453 Iterator<CmsResource> resIter = resourcesToPublish.iterator(); 454 while (resIter.hasNext()) { 455 CmsResource currentRes = resIter.next(); 456 if (publish.getRelationValidator().keySet().contains(currentRes.getRootPath())) { 457 resIter.remove(); 458 LOG.info( 459 "Excluding resource from publish list because relations would be broken: " 460 + currentRes.getRootPath()); 461 } 462 } 463 publish.publishResources(resourcesToPublish); 464 } catch (Exception ex) { 465 LOG.error(ex.getLocalizedMessage(), ex); 466 } 467 } 468 }; 469 thread.start(); 470 CmsWorkflowResponse response = new CmsWorkflowResponse( 471 true, 472 "", 473 new ArrayList<CmsPublishResource>(), 474 new ArrayList<CmsWorkflowAction>(), 475 null); 476 return response; 477 } catch (InterruptedException e) { 478 // shouldn't happen; log exception 479 LOG.error(e.getLocalizedMessage()); 480 return null; 481 } catch (ExecutionException e) { 482 // shouldn't happen; log exception 483 LOG.error(e.getLocalizedMessage()); 484 return null; 485 } 486 } 487}