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}