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