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 GmbH & Co. KG, 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.file.types;
029
030import org.opencms.configuration.CmsParameterConfiguration;
031import org.opencms.db.CmsSecurityManager;
032import org.opencms.file.CmsFile;
033import org.opencms.file.CmsObject;
034import org.opencms.file.CmsProperty;
035import org.opencms.file.CmsRequestContext;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsResource.CmsResourceDeleteMode;
038import org.opencms.file.CmsResourceFilter;
039import org.opencms.loader.CmsXmlContentLoader;
040import org.opencms.lock.CmsLockActionRecord;
041import org.opencms.lock.CmsLockActionRecord.LockChange;
042import org.opencms.lock.CmsLockUtil;
043import org.opencms.main.CmsException;
044import org.opencms.main.CmsIllegalArgumentException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.OpenCms;
047import org.opencms.relations.CmsLink;
048import org.opencms.relations.CmsRelation;
049import org.opencms.relations.CmsRelationFilter;
050import org.opencms.relations.CmsRelationType;
051import org.opencms.security.CmsPermissionSet;
052import org.opencms.staticexport.CmsLinkTable;
053import org.opencms.util.CmsMacroResolver;
054import org.opencms.util.CmsStringUtil;
055import org.opencms.workplace.editors.I_CmsPreEditorActionDefinition;
056import org.opencms.workplace.editors.directedit.I_CmsEditHandler;
057import org.opencms.xml.CmsXmlContentDefinition;
058import org.opencms.xml.CmsXmlEntityResolver;
059import org.opencms.xml.CmsXmlException;
060import org.opencms.xml.containerpage.CmsFormatterConfiguration;
061import org.opencms.xml.content.CmsDefaultXmlContentHandler;
062import org.opencms.xml.content.CmsMappingResolutionContext;
063import org.opencms.xml.content.CmsXmlContent;
064import org.opencms.xml.content.CmsXmlContentFactory;
065import org.opencms.xml.content.I_CmsXmlContentHandler;
066import org.opencms.xml.types.CmsXmlHtmlValue;
067import org.opencms.xml.types.CmsXmlVarLinkValue;
068import org.opencms.xml.types.CmsXmlVfsFileValue;
069import org.opencms.xml.types.I_CmsXmlContentValue;
070import org.opencms.xml.types.I_CmsXmlContentValue.SearchContentType;
071
072import java.util.ArrayList;
073import java.util.Collections;
074import java.util.Iterator;
075import java.util.LinkedHashSet;
076import java.util.List;
077import java.util.Locale;
078import java.util.Set;
079
080import org.apache.commons.logging.Log;
081
082import com.google.common.collect.Lists;
083
084/**
085 * Resource type descriptor for the type "xmlcontent".<p>
086 *
087 * @since 6.0.0
088 */
089public class CmsResourceTypeXmlContent extends A_CmsResourceTypeLinkParseable {
090
091    /** Request context attribute used to enable reverse availability mapping. */
092    public static final String ATTR_REVERSE_AVAILABILITY_MAPPING = "REVERSE_AVAILABILITY_MAPPING";
093
094    /** Configuration key for the (optional) schema. */
095    public static final String CONFIGURATION_SCHEMA = "schema";
096
097    /** The name for the choose model file form action. */
098    public static final String DIALOG_CHOOSEMODEL = "choosemodel";
099
100    /** The name of this resource type. */
101    public static final String RESOURCE_TYPE_NAME = "xmlcontent";
102
103    /** The log object for this class. */
104    private static final Log LOG = CmsLog.getLog(CmsResourceTypeXmlContent.class);
105
106    /** The serial version id. */
107    private static final long serialVersionUID = 2271469830431937731L;
108
109    /** The (optional) schema of this resource. */
110    private String m_schema;
111
112    /**
113     * Returns the possible model files for the new resource.<p>
114     *
115     * @param cms the current users context to work with
116     * @param currentFolder the folder
117     * @param newResourceTypeName the resource type name for the new resource to create
118     * @return the possible model files for the new resource
119     */
120    public static List<CmsResource> getModelFiles(CmsObject cms, String currentFolder, String newResourceTypeName) {
121
122        try {
123
124            I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(newResourceTypeName);
125            I_CmsPreEditorActionDefinition preEditorAction = OpenCms.getWorkplaceManager().getPreEditorConditionDefinition(
126                resType);
127            // get the global master folder if configured
128            String masterFolder = preEditorAction.getConfiguration().getString(
129                CmsDefaultXmlContentHandler.APPINFO_MODELFOLDER,
130                null);
131            // get the schema for the resource type to create
132            String schema = resType.getConfiguration().get(CmsResourceTypeXmlContent.CONFIGURATION_SCHEMA);
133            CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, schema);
134            // get the content handler for the resource type to create
135            I_CmsXmlContentHandler handler = contentDefinition.getContentHandler();
136            String individualModelFolder = handler.getModelFolder();
137            if (CmsStringUtil.isNotEmpty(individualModelFolder)) {
138                masterFolder = individualModelFolder;
139            }
140
141            if (CmsStringUtil.isNotEmpty(masterFolder)) {
142                // store the original URI
143                String uri = cms.getRequestContext().getUri();
144                try {
145                    // set URI to current folder
146                    cms.getRequestContext().setUri(currentFolder);
147                    CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms);
148                    // resolve eventual macros
149                    masterFolder = resolver.resolveMacros(masterFolder);
150                } finally {
151                    // switch back to stored URI
152                    cms.getRequestContext().setUri(uri);
153                }
154
155                if (CmsStringUtil.isNotEmpty(masterFolder) && cms.existsResource(masterFolder)) {
156                    // folder for master files exists, get all files of the same resource type
157                    CmsResourceFilter filter = CmsResourceFilter.ONLY_VISIBLE_NO_DELETED.addRequireType(
158                        resType.getTypeId());
159                    return cms.readResources(masterFolder, filter, false);
160                }
161            }
162        } catch (Throwable t) {
163            // error determining resource type, should never happen
164        }
165        return Collections.emptyList();
166    }
167
168    /**
169     * Returns the static type name of this (default) resource type.<p>
170     *
171     * @return the static type name of this (default) resource type
172     */
173    public static String getStaticTypeName() {
174
175        return RESOURCE_TYPE_NAME;
176    }
177
178    /**
179     * Returns <code>true</code> in case the given resource is an XML content.<p>
180     *
181     * @param resource the resource to check
182     *
183     * @return <code>true</code> in case the given resource is an XML content
184     *
185     * @since 7.0.2
186     */
187    public static boolean isXmlContent(CmsResource resource) {
188
189        boolean result = false;
190        if (resource != null) {
191            // avoid array index out of bound exception:
192            if (!resource.isFolder()) {
193                result = OpenCms.getResourceManager().getResourceType(resource) instanceof CmsResourceTypeXmlContent;
194            }
195        }
196        return result;
197    }
198
199    /**
200     * @see org.opencms.file.types.A_CmsResourceType#addConfigurationParameter(java.lang.String, java.lang.String)
201     */
202    @Override
203    public void addConfigurationParameter(String paramName, String paramValue) {
204
205        super.addConfigurationParameter(paramName, paramValue);
206        if (CONFIGURATION_SCHEMA.equalsIgnoreCase(paramName)) {
207            m_schema = paramValue.trim();
208        }
209    }
210
211    /**
212     * @see org.opencms.file.types.I_CmsResourceType#createResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, java.lang.String, byte[], java.util.List)
213     */
214    @Override
215    public CmsResource createResource(
216        CmsObject cms,
217        CmsSecurityManager securityManager,
218        String resourcename,
219        byte[] content,
220        List<CmsProperty> properties)
221    throws CmsException {
222
223        boolean hasModelUri = false;
224        CmsXmlContent newContent = null;
225        if ((content == null) || (content.length == 0)) {
226
227            // read the default locale for the new resource
228            Locale locale = getLocaleForNewContent(cms, securityManager, resourcename, properties);
229            String modelUri = (String)cms.getRequestContext().getAttribute(CmsRequestContext.ATTRIBUTE_MODEL);
230
231            // must set URI of OpenCms user context to parent folder of created resource,
232            // in order to allow reading of properties for default values
233            CmsObject newCms = OpenCms.initCmsObject(cms);
234            newCms.getRequestContext().setUri(CmsResource.getParentFolder(resourcename));
235            if (modelUri != null) {
236                // create the new content from the model file
237                newContent = CmsXmlContentFactory.createDocument(newCms, locale, modelUri);
238                hasModelUri = true;
239            } else if (m_schema != null) {
240                // unmarshal the content definition for the new resource
241                CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, m_schema);
242
243                // create the new content from the content definition
244                newContent = CmsXmlContentFactory.createDocument(
245                    newCms,
246                    locale,
247                    OpenCms.getSystemInfo().getDefaultEncoding(),
248                    contentDefinition);
249            }
250            // get the bytes from the created content
251            if (newContent != null) {
252                content = newContent.marshal();
253            }
254        }
255
256        // now create the resource using the super class
257        CmsResource resource = super.createResource(cms, securityManager, resourcename, content, properties);
258
259        // a model file was used, call the content handler for post-processing
260        if (hasModelUri) {
261            CmsFile file = cms.readFile(resource);
262            newContent = CmsXmlContentFactory.unmarshal(cms, file);
263            newContent.setAutoCorrectionEnabled(true);
264            resource = newContent.getHandler().prepareForWrite(cms, newContent, file);
265        }
266
267        return resource;
268    }
269
270    /**
271     * @see org.opencms.file.types.A_CmsResourceType#deleteResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, org.opencms.file.CmsResource.CmsResourceDeleteMode)
272     */
273    @Override
274    public void deleteResource(
275        CmsObject cms,
276        CmsSecurityManager securityManager,
277        CmsResource resource,
278        CmsResourceDeleteMode siblingMode)
279    throws CmsException {
280
281        List<CmsResource> detailOnlyPages = null;
282        if (isPossiblyDetailContent(resource)) {
283            detailOnlyPages = getDetailContainerResources(cms, resource);
284        }
285        super.deleteResource(cms, securityManager, resource, siblingMode);
286        if (detailOnlyPages != null) {
287            for (CmsResource page : detailOnlyPages) {
288                if (page.getState().isDeleted()) {
289                    continue;
290                }
291                try {
292                    CmsLockUtil.ensureLock(cms, page);
293                    cms.deleteResource(page, CmsResource.DELETE_PRESERVE_SIBLINGS);
294                } catch (CmsException e) {
295                    LOG.error(e.getLocalizedMessage(), e);
296                }
297            }
298        }
299    }
300
301    /**
302     * @see org.opencms.file.types.I_CmsResourceType#getCachePropertyDefault()
303     */
304    @Override
305    public String getCachePropertyDefault() {
306
307        return "element;locale;";
308    }
309
310    /**
311     * @see org.opencms.file.types.A_CmsResourceType#getConfiguration()
312     */
313    @Override
314    public CmsParameterConfiguration getConfiguration() {
315
316        CmsParameterConfiguration result = new CmsParameterConfiguration();
317        CmsParameterConfiguration additional = super.getConfiguration();
318        if (additional != null) {
319            result.putAll(additional);
320        }
321        if (m_schema != null) {
322            result.put(CONFIGURATION_SCHEMA, m_schema);
323        }
324        return result;
325    }
326
327    /**
328     * Returns the edit handler if configured.<p>
329     *
330     * @param cms the cms context
331     *
332     * @return the edit handler
333     */
334    public I_CmsEditHandler getEditHandler(CmsObject cms) {
335
336        String schema = getSchema();
337
338        try {
339            CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, schema);
340            // get the content handler for the resource type to create
341            I_CmsXmlContentHandler handler = contentDefinition.getContentHandler();
342            return handler.getEditHandler();
343
344        } catch (CmsXmlException e) {
345            LOG.error(e.getMessage(), e);
346        }
347        return null;
348    }
349
350    /**
351     * @see org.opencms.file.types.A_CmsResourceType#getFormattersForResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
352     */
353    @Override
354    public CmsFormatterConfiguration getFormattersForResource(CmsObject cms, CmsResource resource) {
355
356        CmsFormatterConfiguration result = null;
357        CmsXmlContentDefinition cd = null;
358        try {
359            cd = CmsXmlContentDefinition.getContentDefinitionForResource(cms, resource);
360            result = cd.getContentHandler().getFormatterConfiguration(cms, resource);
361        } catch (CmsException e) {
362            // no content definition found, use the preview formatter
363        }
364        if (result == null) {
365            LOG.warn(
366                Messages.get().getBundle().key(
367                    Messages.LOG_WARN_NO_FORMATTERS_DEFINED_1,
368                    cd == null ? resource.getRootPath() : cd.getSchemaLocation()));
369            result = CmsFormatterConfiguration.EMPTY_CONFIGURATION;
370        }
371        return result;
372    }
373
374    /**
375     * @see org.opencms.file.types.A_CmsResourceType#getGalleryPreviewProvider()
376     */
377    @Override
378    public String getGalleryPreviewProvider() {
379
380        if (m_galleryPreviewProvider == null) {
381            m_galleryPreviewProvider = getConfiguration().getString(
382                CONFIGURATION_GALLERY_PREVIEW_PROVIDER,
383                DEFAULT_GALLERY_PREVIEW_PROVIDER);
384        }
385        return m_galleryPreviewProvider;
386    }
387
388    /**
389     * @see org.opencms.file.types.I_CmsResourceType#getLoaderId()
390     */
391    @Override
392    public int getLoaderId() {
393
394        return CmsXmlContentLoader.RESOURCE_LOADER_ID;
395    }
396
397    /**
398     * Returns the configured xsd schema uri.<p>
399     *
400     * @return the configured xsd schema uri, or <code>null</code> if not set
401     */
402    public String getSchema() {
403
404        return m_schema;
405    }
406
407    /**
408     * @see org.opencms.file.types.A_CmsResourceType#initialize(org.opencms.file.CmsObject)
409     */
410    @Override
411    public void initialize(CmsObject cms) {
412
413        super.initialize(cms);
414        if (m_schema != null) {
415            // unmarshal the XML schema, this is required to update the resource bundle cache
416            try {
417                if (cms.existsResource(m_schema)) {
418                    CmsXmlContentDefinition.unmarshal(cms, m_schema);
419                } else {
420                    LOG.debug(
421                        Messages.get().getBundle().key(
422                            Messages.LOG_WARN_SCHEMA_RESOURCE_DOES_NOT_EXIST_2,
423                            m_schema,
424                            getTypeName()));
425                }
426            } catch (Throwable e) {
427                // unable to unmarshal the XML schema configured
428                LOG.error(Messages.get().getBundle().key(Messages.ERR_BAD_XML_SCHEMA_2, m_schema, getTypeName()), e);
429            }
430        }
431    }
432
433    /**
434     * @see org.opencms.file.types.A_CmsResourceType#moveResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, java.lang.String)
435     */
436    @Override
437    public void moveResource(
438        CmsObject cms,
439        CmsSecurityManager securityManager,
440        CmsResource resource,
441        String destination)
442    throws CmsException, CmsIllegalArgumentException {
443
444        super.moveResource(cms, securityManager, resource, destination);
445        if (isPossiblyDetailContent(resource)) {
446            String rootDest = cms.getRequestContext().addSiteRoot(destination);
447            CmsObject rootCms = OpenCms.initCmsObject(cms);
448            rootCms.getRequestContext().setSiteRoot("");
449            String srcParent = CmsResource.getParentFolder(resource.getRootPath());
450            String srcName = CmsResource.getName(resource.getRootPath());
451            String destParent = CmsResource.getParentFolder(rootDest);
452            String destName = CmsResource.getName(rootDest);
453            if (srcParent.equals(destParent) && !srcName.equals(destName)) {
454                List<CmsResource> detailOnlyPages = getDetailContainerResources(cms, resource);
455                for (CmsResource page : detailOnlyPages) {
456                    if (page.getState().isDeleted()) {
457                        continue;
458                    }
459                    String newPath = CmsStringUtil.joinPaths(CmsResource.getParentFolder(page.getRootPath()), destName);
460                    CmsLockActionRecord lockRecord = null;
461                    try {
462                        lockRecord = CmsLockUtil.ensureLock(cms, page);
463                        rootCms.moveResource(page.getRootPath(), newPath);
464                    } catch (Exception e) {
465                        LOG.error(e.getLocalizedMessage(), e);
466                    } finally {
467                        if ((lockRecord != null) && (lockRecord.getChange() == LockChange.locked)) {
468                            try {
469                                CmsLockUtil.tryUnlock(
470                                    rootCms,
471                                    rootCms.readResource(page.getStructureId(), CmsResourceFilter.ALL));
472                            } catch (Exception e) {
473                                LOG.error(e.getLocalizedMessage(), e);
474                            }
475                        }
476                    }
477                }
478            }
479        }
480    }
481
482    /**
483     * @see org.opencms.relations.I_CmsLinkParseable#parseLinks(org.opencms.file.CmsObject, org.opencms.file.CmsFile)
484     */
485    public List<CmsLink> parseLinks(CmsObject cms, CmsFile file) {
486
487        if (file.getLength() == 0) {
488            return Collections.emptyList();
489        }
490        CmsXmlContent xmlContent;
491        long requestTime = cms.getRequestContext().getRequestTime();
492        try {
493            // prevent the check rules to remove the broken links
494            cms.getRequestContext().setRequestTime(CmsResource.DATE_RELEASED_EXPIRED_IGNORE);
495            xmlContent = CmsXmlContentFactory.unmarshal(cms, file);
496        } catch (CmsException e) {
497            if (LOG.isErrorEnabled()) {
498                LOG.error(
499                    org.opencms.db.Messages.get().getBundle().key(
500                        org.opencms.db.Messages.ERR_READ_RESOURCE_1,
501                        cms.getSitePath(file)),
502                    e);
503            }
504            return Collections.emptyList();
505        } finally {
506            cms.getRequestContext().setRequestTime(requestTime);
507        }
508        // using linked set to keep the link order
509        Set<CmsLink> links = new LinkedHashSet<CmsLink>();
510
511        // add XSD link
512        CmsLink xsdLink = getXsdLink(cms, xmlContent);
513        if (xsdLink != null) {
514            links.add(xsdLink);
515        }
516
517        // iterate over all languages
518        List<Locale> locales = xmlContent.getLocales();
519        Iterator<Locale> i = locales.iterator();
520        while (i.hasNext()) {
521            Locale locale = i.next();
522            List<I_CmsXmlContentValue> values = xmlContent.getValues(locale);
523
524            // iterate over all body elements per language
525            Iterator<I_CmsXmlContentValue> j = values.iterator();
526            while (j.hasNext()) {
527                I_CmsXmlContentValue value = j.next();
528                if (value instanceof CmsXmlHtmlValue) {
529                    CmsXmlHtmlValue htmlValue = (CmsXmlHtmlValue)value;
530                    CmsLinkTable linkTable = htmlValue.getLinkTable();
531
532                    // iterate over all links inside a body element
533                    Iterator<CmsLink> k = linkTable.iterator();
534                    while (k.hasNext()) {
535                        CmsLink link = k.next();
536
537                        // external links are omitted
538                        if (link.isInternal()) {
539                            link.checkConsistency(cms);
540                            links.add(link);
541                        }
542                    }
543                } else if (value instanceof CmsXmlVfsFileValue) {
544                    CmsXmlVfsFileValue refValue = (CmsXmlVfsFileValue)value;
545                    CmsLink link = refValue.getLink(cms);
546                    if (link != null) {
547                        links.add(link);
548                    }
549                } else if (value instanceof CmsXmlVarLinkValue) {
550                    CmsXmlVarLinkValue refValue = (CmsXmlVarLinkValue)value;
551                    CmsLink link = refValue.getLink(cms);
552                    if ((link != null) && link.isInternal()) {
553                        links.add(link);
554                    }
555                }
556                if (SearchContentType.CONTENT.equals(xmlContent.getHandler().getSearchContentType(value))) {
557                    String stringValue = value.getStringValue(cms);
558                    try {
559                        if ((null != stringValue) && !stringValue.trim().isEmpty() && cms.existsResource(stringValue)) {
560                            CmsResource res = cms.readResource(stringValue);
561                            if (CmsResourceTypeXmlContent.isXmlContent(res)) {
562                                CmsLink link = new CmsLink(
563                                    "",
564                                    CmsRelationType.INDEX_CONTENT,
565                                    res.getStructureId(),
566                                    res.getRootPath(),
567                                    true);
568                                links.add(link);
569                            }
570                        }
571                    } catch (Throwable t) {
572                        if (LOG.isErrorEnabled()) {
573                            LOG.error(
574                                "Failed to add INDEX_CONTENT relation from resource "
575                                    + file.getRootPath()
576                                    + " to linked resource "
577                                    + stringValue
578                                    + ".",
579                                t);
580                        }
581                    }
582                }
583            }
584        }
585        return new ArrayList<CmsLink>(links);
586    }
587
588    /**
589     * @see org.opencms.file.types.A_CmsResourceType#setDateExpired(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, long, boolean)
590     */
591    @Override
592    public void setDateExpired(
593        CmsObject cms,
594        CmsSecurityManager securityManager,
595        CmsResource resource,
596        long dateExpired,
597        boolean recursive)
598    throws CmsException {
599
600        try {
601            applyReverseAvailabilityMapping(
602                cms,
603                resource,
604                CmsMappingResolutionContext.AttributeType.expiration,
605                dateExpired);
606        } catch (Exception e) {
607            LOG.error("Reverse availability mapping failed: " + e.getLocalizedMessage(), e);
608        }
609        super.setDateExpired(cms, securityManager, resource, dateExpired, recursive);
610    }
611
612    /**
613     * @see org.opencms.file.types.A_CmsResourceType#setDateReleased(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, long, boolean)
614     */
615    @Override
616    public void setDateReleased(
617        CmsObject cms,
618        CmsSecurityManager securityManager,
619        CmsResource resource,
620        long dateReleased,
621        boolean recursive)
622    throws CmsException {
623
624        try {
625            applyReverseAvailabilityMapping(
626                cms,
627                resource,
628                CmsMappingResolutionContext.AttributeType.release,
629                dateReleased);
630        } catch (Exception e) {
631            LOG.error("Reverse availability mapping failed: " + e.getLocalizedMessage(), e);
632        }
633        super.setDateReleased(cms, securityManager, resource, dateReleased, recursive);
634    }
635
636    /**
637     * @see org.opencms.file.types.I_CmsResourceType#writeFile(org.opencms.file.CmsObject, CmsSecurityManager, CmsFile)
638     */
639    @Override
640    public CmsFile writeFile(CmsObject cms, CmsSecurityManager securityManager, CmsFile resource) throws CmsException {
641
642        // check if the user has write access and if resource is locked
643        // done here so that all the XML operations are not performed if permissions not granted
644        securityManager.checkPermissions(
645            cms.getRequestContext(),
646            resource,
647            CmsPermissionSet.ACCESS_WRITE,
648            true,
649            CmsResourceFilter.ALL);
650        // read the XML content, use the encoding set in the property
651        CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(cms, resource, true);
652        // call the content handler for post-processing
653        resource = xmlContent.getHandler().prepareForWrite(cms, xmlContent, resource);
654
655        // now write the file
656        return super.writeFile(cms, securityManager, resource);
657    }
658
659    /**
660     * Gets the locale which should be used for creating an empty content.<p>
661     *
662     * @param cms the current CMS context
663     * @param securityManager the security manager
664     * @param resourcename the name of the resource to create
665     * @param properties the properties for the resource to create
666     *
667     * @return the locale to use
668     */
669    protected Locale getLocaleForNewContent(
670        CmsObject cms,
671        CmsSecurityManager securityManager,
672        String resourcename,
673        List<CmsProperty> properties) {
674
675        Locale locale = (Locale)(cms.getRequestContext().getAttribute(CmsRequestContext.ATTRIBUTE_NEW_RESOURCE_LOCALE));
676        if (locale != null) {
677            return locale;
678        }
679        List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(
680            cms,
681            CmsResource.getParentFolder(resourcename));
682        return locales.get(0);
683    }
684
685    /**
686     * Creates a new link object for the schema definition.<p>
687     *
688     * @param cms the current CMS context
689     * @param xmlContent the xml content to crete the link for
690     *
691     * @return the generated link
692     */
693    protected CmsLink getXsdLink(CmsObject cms, CmsXmlContent xmlContent) {
694
695        String schema = xmlContent.getContentDefinition().getSchemaLocation();
696        if (schema.startsWith(CmsXmlEntityResolver.OPENCMS_SCHEME)) {
697            if (CmsXmlEntityResolver.isInternalId(schema)) {
698                return null;
699            }
700            schema = schema.substring(CmsXmlEntityResolver.OPENCMS_SCHEME.length() - 1);
701        } else if (CmsXmlEntityResolver.isCachedSystemId(schema)) {
702            // schema may not exist as a VFS file because it has just been cached (some test cases do this)
703            return null;
704        }
705        try {
706            CmsResource schemaRes = cms.readResource(cms.getRequestContext().removeSiteRoot(schema));
707            CmsLink xsdLink = new CmsLink(
708                null,
709                CmsRelationType.XSD,
710                schemaRes.getStructureId(),
711                schemaRes.getRootPath(),
712                true);
713            return xsdLink;
714        } catch (CmsException e) {
715            LOG.error(e.getLocalizedMessage(), e);
716        }
717        return null;
718    }
719
720    /**
721     * Checks if the resource is possibly a detail content.<p>
722     *
723     * @param resource the resource to check
724     * @return true if the resource is possibly a detail content
725     */
726    boolean isPossiblyDetailContent(CmsResource resource) {
727
728        if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
729            return false;
730        }
731        I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource);
732        if (type instanceof CmsResourceTypeXmlAdeConfiguration) {
733            return false;
734        }
735        return true;
736    }
737
738    /**
739     * Writes the availability data to the content if possible.
740     *
741     * @param cms the CMS context
742     * @param resource the resource
743     * @param attr the attribute that should be written
744     * @param date the date to be written
745     *
746     * @return true if the availability could be written to the content
747     *
748     * @throws CmsException if something goes wrong
749     */
750    private boolean applyReverseAvailabilityMapping(
751        CmsObject cms,
752        CmsResource resource,
753        CmsMappingResolutionContext.AttributeType attr,
754        long date)
755    throws CmsException {
756
757        Object obj = cms.getRequestContext().getAttribute(ATTR_REVERSE_AVAILABILITY_MAPPING);
758        if ((obj == null) || !Boolean.TRUE.equals(obj)) {
759            return false;
760        }
761        CmsXmlContentDefinition contentDef = CmsXmlContentDefinition.getContentDefinitionForResource(cms, resource);
762        I_CmsXmlContentHandler handler = contentDef.getContentHandler();
763        if (handler.canUseReverseAvailabilityMapping(attr)) {
764
765            CmsFile file = cms.readFile(resource);
766            CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
767            List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(cms, resource);
768            handler.applyReverseAvailabilityMapping(cms, content, attr, locales, date);
769
770            CmsObject writeCms = OpenCms.initCmsObject(cms); // clone CmsObject  to get rid of the request attribute triggering the reverse mapping
771            file.setContents(content.marshal());
772            writeCms.writeFile(file);
773            return true;
774        } else {
775            LOG.debug("No reverse availability mapping.");
776        }
777        return false;
778    }
779
780    /**
781     * Reads the detail container resources which are connected by relations to the given resource.
782     *
783     * @param cms the current CMS context
784     * @param res the detail content
785     *
786     * @return the list of detail only container resources
787     *
788     * @throws CmsException if something goes wrong
789     */
790    private List<CmsResource> getDetailContainerResources(CmsObject cms, CmsResource res) throws CmsException {
791
792        CmsRelationFilter filter = CmsRelationFilter.relationsFromStructureId(res.getStructureId()).filterType(
793            CmsRelationType.DETAIL_ONLY);
794        List<CmsResource> result = Lists.newArrayList();
795        List<CmsRelation> relations = cms.readRelations(filter);
796        for (CmsRelation relation : relations) {
797            try {
798                result.add(relation.getTarget(cms, CmsResourceFilter.ALL));
799            } catch (Exception e) {
800                LOG.error(e.getLocalizedMessage(), e);
801            }
802        }
803        return result;
804    }
805
806}