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