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