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