All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.ugc.CmsUgcSession Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

There is a newer version: 17.0
Show newest version
/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.ugc;

import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsResource;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.lock.CmsLock;
import org.opencms.main.CmsContextInfo;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.I_CmsSessionDestroyHandler;
import org.opencms.main.OpenCms;
import org.opencms.report.CmsLogReport;
import org.opencms.ugc.shared.CmsUgcConstants;
import org.opencms.ugc.shared.CmsUgcException;
import org.opencms.util.CmsMacroResolver;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.xml.CmsXmlException;
import org.opencms.xml.CmsXmlUtils;
import org.opencms.xml.content.CmsXmlContent;
import org.opencms.xml.content.CmsXmlContentErrorHandler;
import org.opencms.xml.content.CmsXmlContentFactory;
import org.opencms.xml.types.I_CmsXmlContentValue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.logging.Log;

import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;

/**
 * A form editing session is required to create and edit contents from the web front-end.

*/ public class CmsUgcSession implements I_CmsSessionDestroyHandler { /** * Compares XPaths.

*/ public static class PathComparator implements Comparator { /** Ordering for comparing single xpath components. */ private Ordering m_elementOrdering; /** * Constructor.

* * @param isDeleteOrder true if ordering for deletes is required */ public PathComparator(boolean isDeleteOrder) { if (isDeleteOrder) { m_elementOrdering = new Ordering() { @Override public int compare(String first, String second) { return ComparisonChain.start().compare( CmsXmlUtils.removeXpathIndex(first), CmsXmlUtils.removeXpathIndex(second)) // use reverse order on indexed elements to avoid delete issues .compare( CmsXmlUtils.getXpathIndexInt(second), CmsXmlUtils.getXpathIndexInt(first)).result(); } }; } else { m_elementOrdering = new Ordering() { @Override public int compare(String first, String second) { return ComparisonChain.start().compare( CmsXmlUtils.removeXpathIndex(first), CmsXmlUtils.removeXpathIndex(second)) // use regular order on indexed elements .compare( CmsXmlUtils.getXpathIndexInt(first), CmsXmlUtils.getXpathIndexInt(second)).result(); } }; } } /** * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ public int compare(String o1, String o2) { int result = -1; if (o1 == null) { result = 1; } else if (o2 == null) { result = -1; } else { String[] o1Elements = o1.split("/"); String[] o2Elements = o2.split("/"); result = m_elementOrdering.lexicographical().compare( Arrays.asList(o1Elements), Arrays.asList(o2Elements)); } return result; } } /** The log instance for this class. */ private static final Log LOG = CmsLog.getLog(CmsUgcSession.class); /** The form upload helper. */ private CmsUgcUploadHelper m_uploadHelper = new CmsUgcUploadHelper(); /** The edit context. */ private CmsObject m_cms; /** The form configuration. */ private CmsUgcConfiguration m_configuration; /** The resource being edited. */ private CmsResource m_editResource; /** The Admin-privileged CMS context. */ private CmsObject m_adminCms; /** True if the session is finished. */ private boolean m_finished; /** Previously uploaded resources, indexed by field names. */ private Map m_uploadResourcesByField = Maps.newHashMap(); /** Flag which indicates whether the project and its resources should be deleted when the session is destroyed. */ private boolean m_requiresCleanup = true; /** * Constructor.

* * @param adminCms the cms context with admin privileges * @param cms the cms context * @param configuration the form configuration * * @throws CmsException if creating the session project fails */ public CmsUgcSession(CmsObject adminCms, CmsObject cms, CmsUgcConfiguration configuration) throws CmsException { m_adminCms = OpenCms.initCmsObject(adminCms); m_configuration = configuration; if ((cms.getRequestContext().getCurrentUser().isGuestUser() || configuration.getForceUserSubstitution()) && m_configuration.getUserForGuests().isPresent()) { m_cms = OpenCms.initCmsObject( adminCms, new CmsContextInfo(m_configuration.getUserForGuests().get().getName())); m_cms.getRequestContext().setSiteRoot(cms.getRequestContext().getSiteRoot()); } else { m_cms = OpenCms.initCmsObject(cms); } for (CmsObject currentCms : new CmsObject[] {m_cms, m_adminCms}) { currentCms.getRequestContext().setLocale(getMessageLocale()); } CmsProject project = m_adminCms.createProject( generateProjectName(), "User generated content project for " + configuration.getPath(), m_configuration.getProjectGroup().getName(), m_configuration.getProjectGroup().getName()); project.setDeleteAfterPublishing(true); project.setFlags(CmsProject.PROJECT_HIDDEN_IN_SELECTOR); m_adminCms.writeProject(project); m_cms.getRequestContext().setCurrentProject(project); } /** * Constructor.

* * @param cms the cms context * @param configuration the form configuration * * @throws CmsException if creating the session project fails */ public CmsUgcSession(CmsObject cms, CmsUgcConfiguration configuration) throws CmsException { this(cms, cms, configuration); } /** * Constructor. For test purposes only.

* * @param cms the cms context */ protected CmsUgcSession(CmsObject cms) { m_cms = cms; } /** * Creates a new resource from upload data.

* * @param fieldName the name of the form field for the upload * @param rawFileName the file name * @param content the file content * * @return the newly created resource * * @throws CmsUgcException if creating the resource fails */ public CmsResource createUploadResource(String fieldName, String rawFileName, byte[] content) throws CmsUgcException { CmsResource result = null; CmsUgcSessionSecurityUtil.checkCreateUpload(m_cms, m_configuration, rawFileName, content.length); String baseName = rawFileName; // if the given name is a path, make sure we only get the last segment int lastSlashPos = Math.max(baseName.lastIndexOf('/'), baseName.lastIndexOf('\\')); if (lastSlashPos != -1) { baseName = baseName.substring(1 + lastSlashPos); } // translate it so it doesn't contain illegal characters baseName = OpenCms.getResourceManager().getFileTranslator().translateResource(baseName); // add a macro before the file extension (if there is a file extension, otherwise just append it) int dotPos = baseName.lastIndexOf('.'); if (dotPos == -1) { baseName = baseName + "_%(random)"; } else { baseName = baseName.substring(0, dotPos) + "_%(random)" + baseName.substring(dotPos); } // now prepend the upload folder's path String uploadRootPath = m_configuration.getUploadParentFolder().get().getRootPath(); String sitePath = CmsStringUtil.joinPaths(m_cms.getRequestContext().removeSiteRoot(uploadRootPath), baseName); // ... and replace the macro with random strings until we find a path that isn't already used String realSitePath; do { CmsMacroResolver resolver = new CmsMacroResolver(); resolver.addMacro("random", RandomStringUtils.random(8, "0123456789abcdefghijklmnopqrstuvwxyz")); realSitePath = resolver.resolveMacros(sitePath); } while (m_cms.existsResource(realSitePath)); try { I_CmsResourceType resType = OpenCms.getResourceManager().getDefaultTypeForName(realSitePath); result = m_cms.createResource(realSitePath, resType, content, null); updateUploadResource(fieldName, result); return result; } catch (CmsException e) { LOG.error(e.getLocalizedMessage(), e); throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); } } /** * Creates a new edit resource.

* * @return the newly created resource * * @throws CmsUgcException if creating the resource fails */ public CmsResource createXmlContent() throws CmsUgcException { checkNotFinished(); checkEditResourceNotSet(); CmsUgcSessionSecurityUtil.checkCreateContent(m_cms, m_configuration); try { I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(m_configuration.getResourceType()); m_editResource = m_cms.createResource(getNewContentName(), type); return m_editResource; } catch (CmsException e) { LOG.error(e.getLocalizedMessage(), e); throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); } } /** * Disables auto-cleanup on session destruction.

*/ public void disableCleanup() { m_requiresCleanup = false; } /** * Finishes the session and publishes the changed resources if necessary.

* * @throws CmsException if something goes wrong */ public void finish() throws CmsException { m_finished = true; m_requiresCleanup = false; CmsProject project = getProject(); CmsObject projectCms = OpenCms.initCmsObject(m_adminCms); projectCms.getRequestContext().setCurrentProject(project); if (m_configuration.isAutoPublish()) { // we don't necessarily publish with the user who has the locks on the resources, so we need to steal the locks List projectResources = projectCms.readProjectView(project.getUuid(), CmsResource.STATE_KEEP); for (CmsResource projectResource : projectResources) { CmsLock lock = projectCms.getLock(projectResource); if (!lock.isUnlocked() && !lock.isLockableBy(projectCms.getRequestContext().getCurrentUser())) { projectCms.changeLock(projectResource); } } OpenCms.getPublishManager().publishProject( projectCms, new CmsLogReport(Locale.ENGLISH, CmsUgcSession.class)); } else { // try to unlock everything - we don't need this in case of auto-publish, since publishing already unlocks the resources projectCms.unlockProject(project.getUuid()); } } /** * Gets the CMS context used by this session.

* * @return the CMS context used by this session */ public CmsObject getCmsObject() { return m_cms; } /** * Gets the form upload helper belonging to this session.

* * @return the form upload helper belonging to this session */ public CmsUgcUploadHelper getFormUploadHelper() { return m_uploadHelper; } /** * Returns the session id.

* * @return the session id */ public CmsUUID getId() { return getProject().getUuid(); } /** * Returns the locale to use for messages generated by the form session which are intended to be displayed on the client.

* * @return the locale to use for messages */ public Locale getMessageLocale() { return m_configuration.getLocale(); } /** * Returns the edit project.

* * @return the edit project */ public CmsProject getProject() { return m_cms.getRequestContext().getCurrentProject(); } /** * Returns the edit resource.

* * @return the edit resource */ public CmsResource getResource() { return m_editResource; } /** * Returns the content values.

* * @return the content values * * @throws CmsException if reading the content fails */ public Map getValues() throws CmsException { CmsFile file = m_cms.readFile(m_editResource); CmsXmlContent content = unmarshalXmlContent(file); Locale locale = m_cms.getRequestContext().getLocale(); if (!content.hasLocale(locale)) { content.addLocale(m_cms, locale); } return getContentValues(content, locale); } /** * Returns true if the session is finished.

* * @return true if the session is finished */ public boolean isFinished() { return m_finished; } /** * Loads the existing edit resource.

* * @param fileName the resource file name * * @return the edit resource * * @throws CmsUgcException if reading the resource fails */ public CmsResource loadXmlContent(String fileName) throws CmsUgcException { checkNotFinished(); checkEditResourceNotSet(); if (fileName.contains("/")) { String message = Messages.get().container(Messages.ERR_INVALID_FILE_NAME_TO_LOAD_1, fileName).key( getCmsObject().getRequestContext().getLocale()); throw new CmsUgcException(CmsUgcConstants.ErrorCode.errMisc, message); } try { String contentSitePath = m_cms.getRequestContext().removeSiteRoot( m_configuration.getContentParentFolder().getRootPath()); String path = CmsStringUtil.joinPaths(contentSitePath, fileName); m_editResource = m_cms.readResource(path); CmsLock lock = m_cms.getLock(m_editResource); if (!lock.isOwnedBy(m_cms.getRequestContext().getCurrentUser())) { m_cms.lockResourceTemporary(m_editResource); } return m_editResource; } catch (CmsException e) { throw new CmsUgcException(CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); } } /** * @see org.opencms.main.I_CmsSessionDestroyHandler#onSessionDestroyed() */ public void onSessionDestroyed() { if (m_requiresCleanup) { cleanupProject(); } else { cleanupProjectIfEmpty(); } } /** * Saves the content values to the sessions edit resource.

* * @param contentValues the content values by XPath * * @return the validation handler * * @throws CmsUgcException if writing the content fails */ public CmsXmlContentErrorHandler saveContent(Map contentValues) throws CmsUgcException { checkNotFinished(); try { CmsFile file = m_cms.readFile(m_editResource); CmsXmlContent content = addContentValues(file, contentValues); CmsXmlContentErrorHandler errorHandler = content.validate(m_cms); if (!errorHandler.hasErrors()) { file.setContents(content.marshal()); // the file content might have been modified during the write operation file = m_cms.writeFile(file); } return errorHandler; } catch (CmsException e) { LOG.error(e.getLocalizedMessage(), e); throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); } } /** * Validates the content values.

* * @param contentValues the content values to validate * * @return the validation handler * * @throws CmsUgcException if reading the content file fails */ public CmsXmlContentErrorHandler validateContent(Map contentValues) throws CmsUgcException { checkNotFinished(); try { CmsFile file = m_cms.readFile(m_editResource); CmsXmlContent content = addContentValues(file, contentValues); return content.validate(m_cms); } catch (CmsException e) { LOG.error(e.getLocalizedMessage(), e); throw new CmsUgcException(e, CmsUgcConstants.ErrorCode.errMisc, e.getLocalizedMessage()); } } /** * Adds the given value to the content document.

* * @param content the content document * @param locale the content locale * @param path the value XPath * @param value the value */ protected void addContentValue(CmsXmlContent content, Locale locale, String path, String value) { boolean hasValue = content.hasValue(path, locale); if (!hasValue) { String[] pathElements = path.split("/"); String currentPath = pathElements[0]; for (int i = 0; i < pathElements.length; i++) { if (i > 0) { currentPath = CmsStringUtil.joinPaths(currentPath, pathElements[i]); } while (!content.hasValue(currentPath, locale)) { content.addValue(m_cms, currentPath, locale, CmsXmlUtils.getXpathIndexInt(currentPath) - 1); } } } content.getValue(path, locale).setStringValue(m_cms, value); } /** * Adds the given values to the content document.

* * @param file the content file * @param contentValues the values to add * * @return the content document * * @throws CmsException if writing the XML fails */ protected CmsXmlContent addContentValues(CmsFile file, Map contentValues) throws CmsException { CmsXmlContent content = unmarshalXmlContent(file); Locale locale = m_cms.getRequestContext().getLocale(); addContentValues(content, locale, contentValues); return content; } /** * Adds the given values to the content document.

* * @param content the content document * @param locale the content locale * @param contentValues the values * * @throws CmsXmlException if writing the XML fails */ protected void addContentValues(CmsXmlContent content, Locale locale, Map contentValues) throws CmsXmlException { if (!content.hasLocale(locale)) { content.addLocale(m_cms, locale); } List paths = new ArrayList(contentValues.keySet()); // first delete all null values // use reverse index ordering for similar elements Collections.sort(paths, new PathComparator(true)); String lastDelete = "///"; for (String path : paths) { // skip values where the parent node has been deleted if ((contentValues.get(path) == null) && !path.startsWith(lastDelete)) { lastDelete = path; deleteContentValue(content, locale, path); } } // now add the new or changed values // use regular ordering Collections.sort(paths, new PathComparator(false)); for (String path : paths) { String value = contentValues.get(path); if (value != null) { addContentValue(content, locale, path, value); } } } /** * Deletes the given value path from the content document.

* * @param content the content document * @param locale the content locale * @param path the value XPath */ protected void deleteContentValue(CmsXmlContent content, Locale locale, String path) { boolean hasValue = content.hasValue(path, locale); if (hasValue) { int index = CmsXmlUtils.getXpathIndexInt(path) - 1; I_CmsXmlContentValue val = content.getValue(path, locale); if (index >= val.getMinOccurs()) { content.removeValue(path, locale, index); } else { val.setStringValue(m_cms, ""); } } } /** * Returns the content values of the requested locale.

* * @param content the content document * @param locale the content locale * * @return the values */ protected Map getContentValues(CmsXmlContent content, Locale locale) { Map result = new HashMap(); List values = content.getValues(locale); for (I_CmsXmlContentValue value : values) { if (value.isSimpleType()) { result.put(value.getPath(), value.getStringValue(m_cms)); } } return result; } /** * Throws an error if the edit resource is already set.

* * @throws CmsUgcException if the edit resource is already set */ private void checkEditResourceNotSet() throws CmsUgcException { if (m_editResource != null) { String message = Messages.get().container(Messages.ERR_CANT_EDIT_MULTIPLE_CONTENTS_IN_SESSION_0).key( getCmsObject().getRequestContext().getLocale()); throw new CmsUgcException(CmsUgcConstants.ErrorCode.errInvalidAction, message); } } /** * Checks that the session is not finished, and throws an exception otherwise.

* * @throws CmsUgcException if the session is finished */ private void checkNotFinished() throws CmsUgcException { if (m_finished) { String message = Messages.get().container(Messages.ERR_FORM_SESSION_ALREADY_FINISHED_0).key( getCmsObject().getRequestContext().getLocale()); throw new CmsUgcException(CmsUgcConstants.ErrorCode.errInvalidAction, message); } } /** * Cleans up the project.

*/ private void cleanupProject() { m_requiresCleanup = false; try { CmsObject cms = OpenCms.initCmsObject(m_adminCms); cms.readProject(getProject().getUuid()); } catch (CmsException e) { return; } try { CmsObject cms = OpenCms.initCmsObject(m_adminCms); cms.getRequestContext().setCurrentProject(getProject()); CmsUUID projectId = getProject().getUuid(); List projectResources = cms.readProjectView(projectId, CmsResource.STATE_KEEP); if (hasOnlyNewResources(projectResources)) { for (CmsResource res : projectResources) { LOG.info("Deleting resource for timed out form session: " + res.getRootPath()); deleteResourceFromProject(cms, res); } LOG.info( "Deleting project for timed out form session: " + getProject().getName() + " [" + getProject().getUuid() + "]"); cms.deleteProject(projectId); } } catch (CmsException e) { LOG.error(e.getLocalizedMessage(), e); } } /** * Cleans up the project, but only if it's empty.

*/ private void cleanupProjectIfEmpty() { m_requiresCleanup = false; try { CmsObject cms = OpenCms.initCmsObject(m_adminCms); cms.readProject(getProject().getUuid()); } catch (CmsException e) { return; } try { CmsObject cms = OpenCms.initCmsObject(m_adminCms); cms.getRequestContext().setCurrentProject(getProject()); CmsUUID projectId = getProject().getUuid(); List projectResources = cms.readProjectView(projectId, CmsResource.STATE_KEEP); if (projectResources.isEmpty()) { cms.deleteProject(projectId); } } catch (CmsException e) { LOG.error(e.getLocalizedMessage(), e); } } /** * Deletes the given resource which is part of a form session project.

* * @param cms the CMS context to use * @param res the resource to delete * * @throws CmsException if something goes wrong */ private void deleteResourceFromProject(CmsObject cms, CmsResource res) throws CmsException { CmsLock lock = cms.getLock(res); if (lock.isUnlocked() || lock.isLockableBy(cms.getRequestContext().getCurrentUser())) { cms.lockResourceTemporary(res); } else { cms.changeLock(res); } cms.deleteResource(cms.getSitePath(res), CmsResource.DELETE_PRESERVE_SIBLINGS); } /** * Returns the edit project name.

* * @return the project name */ private String generateProjectName() { return "Edit project " + new Date(); } /** * Returns the new resource site path.

* * @return the new resource site path * @throws CmsException if something goes wrong */ private String getNewContentName() throws CmsException { String sitePath = OpenCms.getResourceManager().getNameGenerator().getNewFileName( m_cms, CmsStringUtil.joinPaths( m_cms.getRequestContext().removeSiteRoot(m_configuration.getContentParentFolder().getRootPath()), m_configuration.getNamePattern()), 5); return sitePath; } /** * Checks if all the resource states from a list of resources are 'new'.

* * @param projectResources the resources to check * @return true if all the resources from the input list have the state 'new' */ private boolean hasOnlyNewResources(List projectResources) { boolean hasOnlyNewResources = true; for (CmsResource projectRes : projectResources) { if (!projectRes.getState().isNew()) { hasOnlyNewResources = false; break; } } return hasOnlyNewResources; } /** * Unmarshal the XML content with auto-correction. * @param file the file that contains the XML * @return the XML read from the file * @throws CmsXmlException thrown if the XML can't be read. */ private CmsXmlContent unmarshalXmlContent(CmsFile file) throws CmsXmlException { CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, file); content.setAutoCorrectionEnabled(true); content.correctXmlStructure(m_cms); return content; } /** * Stores the upload resource and deletes previously uploaded resources for the same form field.

* * @param fieldName the field name * @param upload the uploaded resource */ private void updateUploadResource(String fieldName, CmsResource upload) { CmsResource prevUploadResource = m_uploadResourcesByField.get(fieldName); if (prevUploadResource != null) { try { m_cms.deleteResource(m_cms.getSitePath(prevUploadResource), CmsResource.DELETE_PRESERVE_SIBLINGS); } catch (Exception e) { LOG.error("Couldn't delete previous upload resource: " + e.getLocalizedMessage(), e); } } m_uploadResourcesByField.put(fieldName, upload); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy