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.ugc; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProject; 033import org.opencms.file.CmsResource; 034import org.opencms.file.types.I_CmsResourceType; 035import org.opencms.lock.CmsLock; 036import org.opencms.main.CmsContextInfo; 037import org.opencms.main.CmsException; 038import org.opencms.main.CmsLog; 039import org.opencms.main.I_CmsSessionDestroyHandler; 040import org.opencms.main.OpenCms; 041import org.opencms.report.CmsLogReport; 042import org.opencms.ugc.shared.CmsUgcConstants; 043import org.opencms.ugc.shared.CmsUgcException; 044import org.opencms.util.CmsMacroResolver; 045import org.opencms.util.CmsStringUtil; 046import org.opencms.util.CmsUUID; 047import org.opencms.xml.CmsXmlException; 048import org.opencms.xml.CmsXmlUtils; 049import org.opencms.xml.content.CmsXmlContent; 050import org.opencms.xml.content.CmsXmlContentErrorHandler; 051import org.opencms.xml.content.CmsXmlContentFactory; 052import org.opencms.xml.types.I_CmsXmlContentValue; 053 054import java.util.ArrayList; 055import java.util.Arrays; 056import java.util.Collections; 057import java.util.Comparator; 058import java.util.Date; 059import java.util.HashMap; 060import java.util.List; 061import java.util.Locale; 062import java.util.Map; 063 064import org.apache.commons.lang3.RandomStringUtils; 065import org.apache.commons.logging.Log; 066 067import com.google.common.collect.ComparisonChain; 068import com.google.common.collect.Maps; 069import com.google.common.collect.Ordering; 070 071/** 072 * A form editing session is required to create and edit contents from the web front-end.<p> 073 */ 074public class CmsUgcSession implements I_CmsSessionDestroyHandler { 075 076 /** 077 * Compares XPaths.<p> 078 */ 079 public static class PathComparator implements Comparator<String> { 080 081 /** Ordering for comparing single xpath components. */ 082 private Ordering<String> m_elementOrdering; 083 084 /** 085 * Constructor.<p> 086 * 087 * @param isDeleteOrder <code>true</code> if ordering for deletes is required 088 */ 089 public PathComparator(boolean isDeleteOrder) { 090 091 if (isDeleteOrder) { 092 m_elementOrdering = new Ordering<String>() { 093 094 @Override 095 public int compare(String first, String second) { 096 097 return ComparisonChain.start().compare( 098 CmsXmlUtils.removeXpathIndex(first), 099 CmsXmlUtils.removeXpathIndex(second)) 100 // use reverse order on indexed elements to avoid delete issues 101 .compare( 102 CmsXmlUtils.getXpathIndexInt(second), 103 CmsXmlUtils.getXpathIndexInt(first)).result(); 104 } 105 }; 106 } else { 107 m_elementOrdering = new Ordering<String>() { 108 109 @Override 110 public int compare(String first, String second) { 111 112 return ComparisonChain.start().compare( 113 CmsXmlUtils.removeXpathIndex(first), 114 CmsXmlUtils.removeXpathIndex(second)) 115 // use regular order on indexed elements 116 .compare( 117 CmsXmlUtils.getXpathIndexInt(first), 118 CmsXmlUtils.getXpathIndexInt(second)).result(); 119 } 120 }; 121 } 122 } 123 124 /** 125 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 126 */ 127 public int compare(String o1, String o2) { 128 129 int result = -1; 130 if (o1 == null) { 131 result = 1; 132 } else if (o2 == null) { 133 result = -1; 134 } else { 135 String[] o1Elements = o1.split("/"); 136 String[] o2Elements = o2.split("/"); 137 result = m_elementOrdering.lexicographical().compare( 138 Arrays.asList(o1Elements), 139 Arrays.asList(o2Elements)); 140 } 141 return result; 142 } 143 } 144 145 /** The log instance for this class. */ 146 private static final Log LOG = CmsLog.getLog(CmsUgcSession.class); 147 148 /** The form upload helper. */ 149 private CmsUgcUploadHelper m_uploadHelper = new CmsUgcUploadHelper(); 150 151 /** The edit context. */ 152 private CmsObject m_cms; 153 154 /** The form configuration. */ 155 private CmsUgcConfiguration m_configuration; 156 157 /** The resource being edited. */ 158 private CmsResource m_editResource; 159 160 /** The Admin-privileged CMS context. */ 161 private CmsObject m_adminCms; 162 163 /** True if the session is finished. */ 164 private boolean m_finished; 165 166 /** Previously uploaded resources, indexed by field names. */ 167 private Map<String, CmsResource> m_uploadResourcesByField = Maps.newHashMap(); 168 169 /** Flag which indicates whether the project and its resources should be deleted when the session is destroyed. */ 170 private boolean m_requiresCleanup = true; 171 172 /** 173 * Constructor.<p> 174 * 175 * @param adminCms the cms context with admin privileges 176 * @param cms the cms context 177 * @param configuration the form configuration 178 * 179 * @throws CmsException if creating the session project fails 180 */ 181 public CmsUgcSession(CmsObject adminCms, CmsObject cms, CmsUgcConfiguration configuration) 182 throws CmsException { 183 184 m_adminCms = OpenCms.initCmsObject(adminCms); 185 m_configuration = configuration; 186 if (cms.getRequestContext().getCurrentUser().isGuestUser() && m_configuration.getUserForGuests().isPresent()) { 187 m_cms = OpenCms.initCmsObject( 188 adminCms, 189 new CmsContextInfo(m_configuration.getUserForGuests().get().getName())); 190 m_cms.getRequestContext().setSiteRoot(cms.getRequestContext().getSiteRoot()); 191 } else { 192 m_cms = OpenCms.initCmsObject(cms); 193 } 194 for (CmsObject currentCms : new CmsObject[] {m_cms, m_adminCms}) { 195 currentCms.getRequestContext().setLocale(getMessageLocale()); 196 } 197 CmsProject project = m_adminCms.createProject( 198 generateProjectName(), 199 "User generated content project for " + configuration.getPath(), 200 m_configuration.getProjectGroup().getName(), 201 m_configuration.getProjectGroup().getName()); 202 project.setDeleteAfterPublishing(true); 203 project.setFlags(CmsProject.PROJECT_HIDDEN_IN_SELECTOR); 204 m_adminCms.writeProject(project); 205 m_cms.getRequestContext().setCurrentProject(project); 206 } 207 208 /** 209 * Constructor.<p> 210 * 211 * @param cms the cms context 212 * @param configuration the form configuration 213 * 214 * @throws CmsException if creating the session project fails 215 */ 216 public CmsUgcSession(CmsObject cms, CmsUgcConfiguration configuration) 217 throws CmsException { 218 219 this(cms, cms, configuration); 220 } 221 222 /** 223 * Constructor. For test purposes only.<p> 224 * 225 * @param cms the cms context 226 */ 227 protected CmsUgcSession(CmsObject cms) { 228 229 m_cms = cms; 230 } 231 232 /** 233 * Creates a new resource from upload data.<p> 234 * 235 * @param fieldName the name of the form field for the upload 236 * @param rawFileName the file name 237 * @param content the file content 238 * 239 * @return the newly created resource 240 * 241 * @throws CmsUgcException if creating the resource fails 242 */ 243 public CmsResource createUploadResource(String fieldName, String rawFileName, byte[] content) 244 throws CmsUgcException { 245 246 CmsResource result = null; 247 CmsUgcSessionSecurityUtil.checkCreateUpload(m_cms, m_configuration, rawFileName, content.length); 248 String baseName = rawFileName; 249 250 // if the given name is a path, make sure we only get the last segment 251 252 int lastSlashPos = Math.max(baseName.lastIndexOf('/'), baseName.lastIndexOf('\\')); 253 if (lastSlashPos != -1) { 254 baseName = baseName.substring(1 + lastSlashPos); 255 } 256 257 // translate it so it doesn't contain illegal characters 258 259 baseName = OpenCms.getResourceManager().getFileTranslator().translateResource(baseName); 260 261 // add a macro before the file extension (if there is a file extension, otherwise just append it) 262 263 int dotPos = baseName.lastIndexOf('.'); 264 if (dotPos == -1) { 265 baseName = baseName + "_%(random)"; 266 } else { 267 baseName = baseName.substring(0, dotPos) + "_%(random)" + baseName.substring(dotPos); 268 } 269 270 // now prepend the upload folder's path 271 272 String uploadRootPath = m_configuration.getUploadParentFolder().get().getRootPath(); 273 String sitePath = CmsStringUtil.joinPaths(m_cms.getRequestContext().removeSiteRoot(uploadRootPath), baseName); 274 275 // ... and replace the macro with random strings until we find a path that isn't already used 276 277 String realSitePath; 278 do { 279 CmsMacroResolver resolver = new CmsMacroResolver(); 280 resolver.addMacro("random", RandomStringUtils.random(8, "0123456789abcdefghijklmnopqrstuvwxyz")); 281 realSitePath = resolver.resolveMacros(sitePath); 282 } while (m_cms.existsResource(realSitePath)); 283 try { 284 I_CmsResourceType resType = OpenCms.getResourceManager().getDefaultTypeForName(realSitePath); 285 result = m_cms.createResource(realSitePath, resType, content, null); 286 updateUploadResource(fieldName, result); 287 return result; 288 } catch (CmsException e) { 289 LOG.error(e.getLocalizedMessage(), e); 290 throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); 291 } 292 } 293 294 /** 295 * Creates a new edit resource.<p> 296 * 297 * @return the newly created resource 298 * 299 * @throws CmsUgcException if creating the resource fails 300 */ 301 public CmsResource createXmlContent() throws CmsUgcException { 302 303 checkNotFinished(); 304 checkEditResourceNotSet(); 305 306 CmsUgcSessionSecurityUtil.checkCreateContent(m_cms, m_configuration); 307 try { 308 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(m_configuration.getResourceType()); 309 m_editResource = m_cms.createResource(getNewContentName(), type); 310 return m_editResource; 311 } catch (CmsException e) { 312 LOG.error(e.getLocalizedMessage(), e); 313 throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); 314 } 315 } 316 317 /** 318 * Disables auto-cleanup on session destruction.<p> 319 */ 320 public void disableCleanup() { 321 322 m_requiresCleanup = false; 323 } 324 325 /** 326 * Finishes the session and publishes the changed resources if necessary.<p> 327 * 328 * @throws CmsException if something goes wrong 329 */ 330 public void finish() throws CmsException { 331 332 m_finished = true; 333 m_requiresCleanup = false; 334 CmsProject project = getProject(); 335 CmsObject projectCms = OpenCms.initCmsObject(m_adminCms); 336 projectCms.getRequestContext().setCurrentProject(project); 337 if (m_configuration.isAutoPublish()) { 338 // we don't necessarily publish with the user who has the locks on the resources, so we need to steal the locks 339 List<CmsResource> projectResources = projectCms.readProjectView(project.getUuid(), CmsResource.STATE_KEEP); 340 for (CmsResource projectResource : projectResources) { 341 CmsLock lock = projectCms.getLock(projectResource); 342 if (!lock.isUnlocked() && !lock.isLockableBy(projectCms.getRequestContext().getCurrentUser())) { 343 projectCms.changeLock(projectResource); 344 } 345 } 346 OpenCms.getPublishManager().publishProject( 347 projectCms, 348 new CmsLogReport(Locale.ENGLISH, CmsUgcSession.class)); 349 } else { 350 // try to unlock everything - we don't need this in case of auto-publish, since publishing already unlocks the resources 351 projectCms.unlockProject(project.getUuid()); 352 } 353 354 } 355 356 /** 357 * Gets the CMS context used by this session.<p> 358 * 359 * @return the CMS context used by this session 360 */ 361 public CmsObject getCmsObject() { 362 363 return m_cms; 364 } 365 366 /** 367 * Gets the form upload helper belonging to this session.<p> 368 * 369 * @return the form upload helper belonging to this session 370 */ 371 public CmsUgcUploadHelper getFormUploadHelper() { 372 373 return m_uploadHelper; 374 } 375 376 /** 377 * Returns the session id.<p> 378 * 379 * @return the session id 380 */ 381 public CmsUUID getId() { 382 383 return getProject().getUuid(); 384 } 385 386 /** 387 * Returns the locale to use for messages generated by the form session which are intended to be displayed on the client.<p> 388 * 389 * @return the locale to use for messages 390 */ 391 public Locale getMessageLocale() { 392 393 return m_configuration.getLocale(); 394 } 395 396 /** 397 * Returns the edit project.<p> 398 * 399 * @return the edit project 400 */ 401 public CmsProject getProject() { 402 403 return m_cms.getRequestContext().getCurrentProject(); 404 } 405 406 /** 407 * Returns the edit resource.<p> 408 * 409 * @return the edit resource 410 */ 411 public CmsResource getResource() { 412 413 return m_editResource; 414 415 } 416 417 /** 418 * Returns the content values.<p> 419 * 420 * @return the content values 421 * 422 * @throws CmsException if reading the content fails 423 */ 424 public Map<String, String> getValues() throws CmsException { 425 426 CmsFile file = m_cms.readFile(m_editResource); 427 CmsXmlContent content = unmarshalXmlContent(file); 428 Locale locale = m_cms.getRequestContext().getLocale(); 429 if (!content.hasLocale(locale)) { 430 content.addLocale(m_cms, locale); 431 } 432 return getContentValues(content, locale); 433 } 434 435 /** 436 * Returns true if the session is finished.<p> 437 * 438 * @return true if the session is finished 439 */ 440 public boolean isFinished() { 441 442 return m_finished; 443 } 444 445 /** 446 * Loads the existing edit resource.<p> 447 * 448 * @param fileName the resource file name 449 * 450 * @return the edit resource 451 * 452 * @throws CmsUgcException if reading the resource fails 453 */ 454 public CmsResource loadXmlContent(String fileName) throws CmsUgcException { 455 456 checkNotFinished(); 457 checkEditResourceNotSet(); 458 if (fileName.contains("/")) { 459 String message = Messages.get().container(Messages.ERR_INVALID_FILE_NAME_TO_LOAD_1, fileName).key( 460 getCmsObject().getRequestContext().getLocale()); 461 throw new CmsUgcException(CmsUgcConstants.ErrorCode.errMisc, message); 462 } 463 try { 464 String contentSitePath = m_cms.getRequestContext().removeSiteRoot( 465 m_configuration.getContentParentFolder().getRootPath()); 466 String path = CmsStringUtil.joinPaths(contentSitePath, fileName); 467 m_editResource = m_cms.readResource(path); 468 CmsLock lock = m_cms.getLock(m_editResource); 469 if (!lock.isOwnedBy(m_cms.getRequestContext().getCurrentUser())) { 470 m_cms.lockResourceTemporary(m_editResource); 471 } 472 return m_editResource; 473 } catch (CmsException e) { 474 throw new CmsUgcException(CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); 475 } 476 } 477 478 /** 479 * @see org.opencms.main.I_CmsSessionDestroyHandler#onSessionDestroyed() 480 */ 481 public void onSessionDestroyed() { 482 483 if (m_requiresCleanup) { 484 cleanupProject(); 485 } else { 486 cleanupProjectIfEmpty(); 487 } 488 } 489 490 /** 491 * Saves the content values to the sessions edit resource.<p> 492 * 493 * @param contentValues the content values by XPath 494 * 495 * @return the validation handler 496 * 497 * @throws CmsUgcException if writing the content fails 498 */ 499 public CmsXmlContentErrorHandler saveContent(Map<String, String> contentValues) throws CmsUgcException { 500 501 checkNotFinished(); 502 try { 503 CmsFile file = m_cms.readFile(m_editResource); 504 CmsXmlContent content = addContentValues(file, contentValues); 505 CmsXmlContentErrorHandler errorHandler = content.validate(m_cms); 506 if (!errorHandler.hasErrors()) { 507 file.setContents(content.marshal()); 508 // the file content might have been modified during the write operation 509 file = m_cms.writeFile(file); 510 } 511 512 return errorHandler; 513 } catch (CmsException e) { 514 LOG.error(e.getLocalizedMessage(), e); 515 throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); 516 } 517 518 } 519 520 /** 521 * Validates the content values.<p> 522 * 523 * @param contentValues the content values to validate 524 * 525 * @return the validation handler 526 * 527 * @throws CmsUgcException if reading the content file fails 528 */ 529 public CmsXmlContentErrorHandler validateContent(Map<String, String> contentValues) throws CmsUgcException { 530 531 checkNotFinished(); 532 try { 533 CmsFile file = m_cms.readFile(m_editResource); 534 CmsXmlContent content = addContentValues(file, contentValues); 535 return content.validate(m_cms); 536 } catch (CmsException e) { 537 LOG.error(e.getLocalizedMessage(), e); 538 throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); 539 } 540 } 541 542 /** 543 * Adds the given value to the content document.<p> 544 * 545 * @param content the content document 546 * @param locale the content locale 547 * @param path the value XPath 548 * @param value the value 549 */ 550 protected void addContentValue(CmsXmlContent content, Locale locale, String path, String value) { 551 552 boolean hasValue = content.hasValue(path, locale); 553 if (!hasValue) { 554 String[] pathElements = path.split("/"); 555 String currentPath = pathElements[0]; 556 for (int i = 0; i < pathElements.length; i++) { 557 if (i > 0) { 558 currentPath = CmsStringUtil.joinPaths(currentPath, pathElements[i]); 559 } 560 while (!content.hasValue(currentPath, locale)) { 561 content.addValue(m_cms, currentPath, locale, CmsXmlUtils.getXpathIndexInt(currentPath) - 1); 562 } 563 } 564 } 565 content.getValue(path, locale).setStringValue(m_cms, value); 566 567 } 568 569 /** 570 * Adds the given values to the content document.<p> 571 * 572 * @param file the content file 573 * @param contentValues the values to add 574 * 575 * @return the content document 576 * 577 * @throws CmsException if writing the XML fails 578 */ 579 protected CmsXmlContent addContentValues(CmsFile file, Map<String, String> contentValues) throws CmsException { 580 581 CmsXmlContent content = unmarshalXmlContent(file); 582 Locale locale = m_cms.getRequestContext().getLocale(); 583 584 addContentValues(content, locale, contentValues); 585 return content; 586 } 587 588 /** 589 * Adds the given values to the content document.<p> 590 * 591 * @param content the content document 592 * @param locale the content locale 593 * @param contentValues the values 594 * 595 * @throws CmsXmlException if writing the XML fails 596 */ 597 protected void addContentValues(CmsXmlContent content, Locale locale, Map<String, String> contentValues) 598 throws CmsXmlException { 599 600 if (!content.hasLocale(locale)) { 601 content.addLocale(m_cms, locale); 602 } 603 List<String> paths = new ArrayList<String>(contentValues.keySet()); 604 // first delete all null values 605 // use reverse index ordering for similar elements 606 Collections.sort(paths, new PathComparator(true)); 607 String lastDelete = "///"; 608 for (String path : paths) { 609 // skip values where the parent node has been deleted 610 if ((contentValues.get(path) == null) && !path.startsWith(lastDelete)) { 611 lastDelete = path; 612 613 deleteContentValue(content, locale, path); 614 } 615 } 616 // now add the new or changed values 617 // use regular ordering 618 Collections.sort(paths, new PathComparator(false)); 619 for (String path : paths) { 620 String value = contentValues.get(path); 621 if (value != null) { 622 addContentValue(content, locale, path, value); 623 } 624 } 625 } 626 627 /** 628 * Deletes the given value path from the content document.<p> 629 * 630 * @param content the content document 631 * @param locale the content locale 632 * @param path the value XPath 633 */ 634 protected void deleteContentValue(CmsXmlContent content, Locale locale, String path) { 635 636 boolean hasValue = content.hasValue(path, locale); 637 if (hasValue) { 638 int index = CmsXmlUtils.getXpathIndexInt(path) - 1; 639 I_CmsXmlContentValue val = content.getValue(path, locale); 640 if (index >= val.getMinOccurs()) { 641 content.removeValue(path, locale, index); 642 } else { 643 val.setStringValue(m_cms, ""); 644 } 645 } 646 } 647 648 /** 649 * Returns the content values of the requested locale.<p> 650 * 651 * @param content the content document 652 * @param locale the content locale 653 * 654 * @return the values 655 */ 656 protected Map<String, String> getContentValues(CmsXmlContent content, Locale locale) { 657 658 Map<String, String> result = new HashMap<String, String>(); 659 List<I_CmsXmlContentValue> values = content.getValues(locale); 660 for (I_CmsXmlContentValue value : values) { 661 if (value.isSimpleType()) { 662 result.put(value.getPath(), value.getStringValue(m_cms)); 663 } 664 } 665 return result; 666 } 667 668 /** 669 * Throws an error if the edit resource is already set.<p> 670 * 671 * @throws CmsUgcException if the edit resource is already set 672 */ 673 private void checkEditResourceNotSet() throws CmsUgcException { 674 675 if (m_editResource != null) { 676 String message = Messages.get().container(Messages.ERR_CANT_EDIT_MULTIPLE_CONTENTS_IN_SESSION_0).key( 677 getCmsObject().getRequestContext().getLocale()); 678 throw new CmsUgcException(CmsUgcConstants.ErrorCode.errInvalidAction, message); 679 680 } 681 } 682 683 /** 684 * Checks that the session is not finished, and throws an exception otherwise.<p> 685 * 686 * @throws CmsUgcException if the session is finished 687 */ 688 private void checkNotFinished() throws CmsUgcException { 689 690 if (m_finished) { 691 String message = Messages.get().container(Messages.ERR_FORM_SESSION_ALREADY_FINISHED_0).key( 692 getCmsObject().getRequestContext().getLocale()); 693 throw new CmsUgcException(CmsUgcConstants.ErrorCode.errInvalidAction, message); 694 } 695 } 696 697 /** 698 * Cleans up the project.<p> 699 */ 700 private void cleanupProject() { 701 702 m_requiresCleanup = false; 703 try { 704 CmsObject cms = OpenCms.initCmsObject(m_adminCms); 705 cms.readProject(getProject().getUuid()); 706 } catch (CmsException e) { 707 return; 708 } 709 try { 710 CmsObject cms = OpenCms.initCmsObject(m_adminCms); 711 cms.getRequestContext().setCurrentProject(getProject()); 712 CmsUUID projectId = getProject().getUuid(); 713 List<CmsResource> projectResources = cms.readProjectView(projectId, CmsResource.STATE_KEEP); 714 if (hasOnlyNewResources(projectResources)) { 715 for (CmsResource res : projectResources) { 716 LOG.info("Deleting resource for timed out form session: " + res.getRootPath()); 717 deleteResourceFromProject(cms, res); 718 } 719 LOG.info( 720 "Deleting project for timed out form session: " 721 + getProject().getName() 722 + " [" 723 + getProject().getUuid() 724 + "]"); 725 cms.deleteProject(projectId); 726 727 } 728 } catch (CmsException e) { 729 LOG.error(e.getLocalizedMessage(), e); 730 } 731 } 732 733 /** 734 * Cleans up the project, but only if it's empty.<p> 735 */ 736 private void cleanupProjectIfEmpty() { 737 738 m_requiresCleanup = false; 739 try { 740 CmsObject cms = OpenCms.initCmsObject(m_adminCms); 741 cms.readProject(getProject().getUuid()); 742 } catch (CmsException e) { 743 return; 744 } 745 try { 746 CmsObject cms = OpenCms.initCmsObject(m_adminCms); 747 cms.getRequestContext().setCurrentProject(getProject()); 748 CmsUUID projectId = getProject().getUuid(); 749 List<CmsResource> projectResources = cms.readProjectView(projectId, CmsResource.STATE_KEEP); 750 if (projectResources.isEmpty()) { 751 cms.deleteProject(projectId); 752 } 753 } catch (CmsException e) { 754 LOG.error(e.getLocalizedMessage(), e); 755 } 756 } 757 758 /** 759 * Deletes the given resource which is part of a form session project.<p> 760 * 761 * @param cms the CMS context to use 762 * @param res the resource to delete 763 * 764 * @throws CmsException if something goes wrong 765 */ 766 private void deleteResourceFromProject(CmsObject cms, CmsResource res) throws CmsException { 767 768 CmsLock lock = cms.getLock(res); 769 if (lock.isUnlocked() || lock.isLockableBy(cms.getRequestContext().getCurrentUser())) { 770 cms.lockResourceTemporary(res); 771 } else { 772 cms.changeLock(res); 773 } 774 cms.deleteResource(cms.getSitePath(res), CmsResource.DELETE_PRESERVE_SIBLINGS); 775 } 776 777 /** 778 * Returns the edit project name.<p> 779 * 780 * @return the project name 781 */ 782 private String generateProjectName() { 783 784 return "Edit project " + new Date(); 785 } 786 787 /** 788 * Returns the new resource site path.<p> 789 * 790 * @return the new resource site path 791 * @throws CmsException if something goes wrong 792 */ 793 private String getNewContentName() throws CmsException { 794 795 String sitePath = OpenCms.getResourceManager().getNameGenerator().getNewFileName( 796 m_cms, 797 CmsStringUtil.joinPaths( 798 m_cms.getRequestContext().removeSiteRoot(m_configuration.getContentParentFolder().getRootPath()), 799 m_configuration.getNamePattern()), 800 5); 801 return sitePath; 802 } 803 804 /** 805 * Checks if all the resource states from a list of resources are 'new'.<p> 806 * 807 * @param projectResources the resources to check 808 * @return true if all the resources from the input list have the state 'new' 809 */ 810 private boolean hasOnlyNewResources(List<CmsResource> projectResources) { 811 812 boolean hasOnlyNewResources = true; 813 for (CmsResource projectRes : projectResources) { 814 if (!projectRes.getState().isNew()) { 815 hasOnlyNewResources = false; 816 break; 817 } 818 } 819 return hasOnlyNewResources; 820 } 821 822 /** 823 * Unmarshal the XML content with auto-correction. 824 * @param file the file that contains the XML 825 * @return the XML read from the file 826 * @throws CmsXmlException thrown if the XML can't be read. 827 */ 828 private CmsXmlContent unmarshalXmlContent(CmsFile file) throws CmsXmlException { 829 830 CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, file); 831 content.setAutoCorrectionEnabled(true); 832 content.correctXmlStructure(m_cms); 833 834 return content; 835 } 836 837 /** 838 * Stores the upload resource and deletes previously uploaded resources for the same form field.<p> 839 * 840 * @param fieldName the field name 841 * @param upload the uploaded resource 842 */ 843 private void updateUploadResource(String fieldName, CmsResource upload) { 844 845 CmsResource prevUploadResource = m_uploadResourcesByField.get(fieldName); 846 if (prevUploadResource != null) { 847 try { 848 m_cms.deleteResource(m_cms.getSitePath(prevUploadResource), CmsResource.DELETE_PRESERVE_SIBLINGS); 849 } catch (Exception e) { 850 LOG.error("Couldn't delete previous upload resource: " + e.getLocalizedMessage(), e); 851 } 852 } 853 m_uploadResourcesByField.put(fieldName, upload); 854 } 855}