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