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.CmsConfigurationCopyResource;
031import org.opencms.configuration.CmsConfigurationException;
032import org.opencms.configuration.CmsParameterConfiguration;
033import org.opencms.db.CmsSecurityManager;
034import org.opencms.file.CmsFile;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProperty;
037import org.opencms.file.CmsResource;
038import org.opencms.file.CmsResourceFilter;
039import org.opencms.file.CmsVfsException;
040import org.opencms.file.CmsVfsResourceNotFoundException;
041import org.opencms.loader.CmsLoaderException;
042import org.opencms.loader.CmsResourceManager;
043import org.opencms.lock.CmsLockType;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsIllegalArgumentException;
046import org.opencms.main.CmsLog;
047import org.opencms.main.CmsRuntimeException;
048import org.opencms.main.OpenCms;
049import org.opencms.relations.CmsLink;
050import org.opencms.relations.I_CmsLinkParseable;
051import org.opencms.staticexport.CmsLinkManager;
052import org.opencms.util.CmsFileUtil;
053import org.opencms.util.CmsMacroResolver;
054import org.opencms.util.CmsStringUtil;
055import org.opencms.xml.containerpage.CmsFormatterConfiguration;
056
057import java.util.ArrayList;
058import java.util.Collections;
059import java.util.HashMap;
060import java.util.Iterator;
061import java.util.List;
062import java.util.Map;
063
064import org.apache.commons.logging.Log;
065
066/**
067 * Base implementation for resource type classes.<p>
068 *
069 * @since 6.0.0
070 */
071public abstract class A_CmsResourceType implements I_CmsResourceType {
072
073    /** Configuration key for optional javascript in galleries. */
074    public static final String CONFIGURATION_GALLERY_JAVASCRIPT_PATH = "gallery.javascript.path";
075
076    /** Configuration key for optional preview provider in galleries. */
077    public static final String CONFIGURATION_GALLERY_PREVIEW_PROVIDER = "gallery.preview.provider";
078
079    /** Configuration key for the optional folder class name. */
080    public static final String CONFIGURATION_GALLERY_TYPE_NAMES = "gallery.type.names";
081
082    /** Configuration key for the (optional) internal flag. */
083    public static final String CONFIGURATION_INTERNAL = "resource.flag.internal";
084
085    /** The default gallery preview provider. */
086    public static final String DEFAULT_GALLERY_PREVIEW_PROVIDER = "org.opencms.ade.galleries.preview.CmsBinaryPreviewProvider";
087
088    /** Macro for the folder path of the current resource. */
089    public static final String MACRO_RESOURCE_FOLDER_PATH = "resource.folder.path";
090
091    /** Macro for the folder path of the current resource, with touch enabled for the copied resources. */
092    public static final String MACRO_RESOURCE_FOLDER_PATH_TOUCH = "resource.folder.path.touch";
093
094    /** Macro for the name of the current resource. */
095    public static final String MACRO_RESOURCE_NAME = "resource.name";
096
097    /** Macro for the parent folder path of the current resource. */
098    public static final String MACRO_RESOURCE_PARENT_PATH = "resource.parent.path";
099
100    /** Macro for the root path of the current resource. */
101    public static final String MACRO_RESOURCE_ROOT_PATH = "resource.root.path";
102
103    /** Macro for the site path of the current resource. */
104    public static final String MACRO_RESOURCE_SITE_PATH = "resource.site.path";
105
106    /** The log object for this class. */
107    private static final Log LOG = CmsLog.getLog(A_CmsResourceType.class);
108
109    /** The serial version id. */
110    private static final long serialVersionUID = 2131071233840674874L;
111
112    /** Flag for showing that this is an additional resource type which is defined in a module. */
113    protected boolean m_addititionalModuleResourceType;
114
115    /** The configured class name of this resource type. */
116    protected String m_className;
117
118    /** Configuration parameters. */
119    protected CmsParameterConfiguration m_configuration;
120
121    /** The list of resources to copy. */
122    protected List<CmsConfigurationCopyResource> m_copyResources;
123
124    /** The list of configured default properties. */
125    protected List<CmsProperty> m_defaultProperties;
126
127    /** Indicates that the configuration of the resource type has been frozen. */
128    protected boolean m_frozen;
129
130    /** The gallery preview provider. */
131    protected String m_galleryPreviewProvider;
132
133    /**  Contains the file extensions mapped to this resource type. */
134    protected List<String> m_mappings;
135
136    /** The module name if this is an additional resource type which is defined in a module. */
137    protected String m_moduleName;
138
139    /** The configured id of this resource type. */
140    protected int m_typeId;
141
142    /** The configured name of this resource type. */
143    protected String m_typeName;
144
145    /** The folder for which links should be adjusted after copying the copy-resources. */
146    private String m_adjustLinksFolder;
147
148    /** The gallery type name for this resource type. */
149    private String m_galleryTypeNames;
150
151    /** The gallery type for this resource type. */
152    private List<I_CmsResourceType> m_galleryTypes;
153
154    /** The optional internal parameter value. */
155    private Boolean m_internal;
156
157    /**
158     * Default constructor, used to initialize some member variables.<p>
159     */
160    public A_CmsResourceType() {
161
162        m_typeId = -1;
163        m_mappings = new ArrayList<String>();
164        m_defaultProperties = new ArrayList<CmsProperty>();
165        m_copyResources = new ArrayList<CmsConfigurationCopyResource>();
166        m_configuration = new CmsParameterConfiguration();
167    }
168
169    /**
170     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String)
171     */
172    public void addConfigurationParameter(String paramName, String paramValue) {
173
174        m_configuration.add(paramName, paramValue);
175        if (CmsStringUtil.isNotEmpty(paramName) && CmsStringUtil.isNotEmpty(paramValue)) {
176            if (CONFIGURATION_INTERNAL.equalsIgnoreCase(paramName)) {
177                m_internal = Boolean.valueOf(paramValue.trim());
178            }
179            if (CONFIGURATION_GALLERY_TYPE_NAMES.equalsIgnoreCase(paramName)) {
180                m_galleryTypeNames = paramValue.trim();
181            }
182        }
183    }
184
185    /**
186     * Adds a new "copy resource" to this resource type,
187     * allowed only during the configuration phase.<p>
188     *
189     * The "copy resources" are copied to the specified location after
190     * a new resource of this type is created. Usually this feature is used to
191     * populate a newly created folder with some default resources.<p>
192     *
193     * If target is <code>null</code>, the macro {@link #MACRO_RESOURCE_FOLDER_PATH} is used as default.
194     * If type is <code>null</code>, the copy type {@link CmsResource#COPY_AS_NEW} is used as default.<p>
195     *
196     * @param source the source resource
197     * @param target the target resource (may contain macros)
198     * @param type the type of the copy, for example "as new", "as sibling" etc
199     *
200     * @throws CmsConfigurationException if the configuration is already frozen
201     */
202    public void addCopyResource(String source, String target, String type) throws CmsConfigurationException {
203
204        if (LOG.isDebugEnabled()) {
205            LOG.debug(
206                Messages.get().getBundle().key(
207                    Messages.LOG_ADD_COPY_RESOURCE_4,
208                    new Object[] {this, source, target, type}));
209        }
210
211        if (m_frozen) {
212            // configuration already frozen
213            throw new CmsConfigurationException(
214                Messages.get().container(
215                    Messages.ERR_CONFIG_FROZEN_3,
216                    this.getClass().getName(),
217                    getTypeName(),
218                    new Integer(getTypeId())));
219        }
220
221        // create the copy resource object an add it to the list
222        CmsConfigurationCopyResource copyResource = new CmsConfigurationCopyResource(source, target, type);
223        m_copyResources.add(copyResource);
224    }
225
226    /**
227     * Adds a default property to this resource type,
228     * allowed only during the configuration phase.<p>
229     *
230     * @param property the default property to add
231     *
232     * @throws CmsConfigurationException if the configuration is already frozen
233     */
234    public void addDefaultProperty(CmsProperty property) throws CmsConfigurationException {
235
236        if (LOG.isDebugEnabled()) {
237            LOG.debug(Messages.get().getBundle().key(Messages.LOG_ADD_DFLT_PROP_2, this, property));
238        }
239
240        if (m_frozen) {
241            // configuration already frozen
242            throw new CmsConfigurationException(
243                Messages.get().container(
244                    Messages.ERR_CONFIG_FROZEN_3,
245                    this.getClass().getName(),
246                    getTypeName(),
247                    new Integer(getTypeId())));
248        }
249
250        m_defaultProperties.add(property);
251    }
252
253    /**
254     * @see org.opencms.file.types.I_CmsResourceType#addMappingType(java.lang.String)
255     */
256    public void addMappingType(String mapping) {
257
258        // this configuration does not support parameters
259        if (LOG.isDebugEnabled()) {
260            LOG.debug(Messages.get().getBundle().key(Messages.LOG_ADD_MAPPING_TYPE_2, mapping, this));
261        }
262        if (m_mappings == null) {
263            m_mappings = new ArrayList<String>();
264        }
265        m_mappings.add(mapping);
266    }
267
268    /**
269     * @see org.opencms.file.types.I_CmsResourceType#changeLock(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource)
270     */
271    public void changeLock(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource)
272    throws CmsException {
273
274        securityManager.changeLock(cms.getRequestContext(), resource);
275    }
276
277    /**
278     * @see org.opencms.file.types.I_CmsResourceType#chflags(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, int)
279     */
280    public void chflags(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, int flags)
281    throws CmsException {
282
283        securityManager.chflags(cms.getRequestContext(), resource, flags);
284    }
285
286    /**
287     * @see org.opencms.file.types.I_CmsResourceType#chtype(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, org.opencms.file.types.I_CmsResourceType)
288     */
289    public void chtype(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, I_CmsResourceType type)
290    throws CmsException {
291
292        // TODO: Refactor driver layer to use resource type id classes (or names) instead of int
293        chtype(cms, securityManager, resource, type.getTypeId());
294    }
295
296    /**
297     * @see org.opencms.file.types.I_CmsResourceType#chtype(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, int)
298     *
299     * @deprecated
300     * Use {@link #chtype(CmsObject, CmsSecurityManager, CmsResource, I_CmsResourceType)} instead.
301     * Resource types should always be referenced either by its type class (preferred) or by type name.
302     * Use of int based resource type references will be discontinued in a future OpenCms release.
303     */
304    @Deprecated
305    public void chtype(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, int type)
306    throws CmsException {
307
308        // change type
309        securityManager.chtype(cms.getRequestContext(), resource, type);
310        // type may have changed from non link parseable to link parseable
311        createRelations(cms, securityManager, resource.getRootPath());
312    }
313
314    /**
315     * @see org.opencms.file.types.I_CmsResourceType#copyResource(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, java.lang.String, CmsResource.CmsResourceCopyMode)
316     */
317    public void copyResource(
318        CmsObject cms,
319        CmsSecurityManager securityManager,
320        CmsResource source,
321        String destination,
322        CmsResource.CmsResourceCopyMode siblingMode)
323    throws CmsException {
324
325        securityManager.copyResource(
326            cms.getRequestContext(),
327            source,
328            cms.getRequestContext().addSiteRoot(destination),
329            siblingMode);
330        // create the relations for the new resource, this could be improved by an sql query for copying relations
331        createRelations(cms, securityManager, cms.getRequestContext().addSiteRoot(destination));
332    }
333
334    /**
335     * @see org.opencms.file.types.I_CmsResourceType#copyResourceToProject(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource)
336     */
337    public void copyResourceToProject(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource)
338    throws CmsException {
339
340        securityManager.copyResourceToProject(cms.getRequestContext(), resource);
341    }
342
343    /**
344     * @see org.opencms.file.types.I_CmsResourceType#createResource(org.opencms.file.CmsObject, CmsSecurityManager, java.lang.String, byte[], List)
345     */
346    public CmsResource createResource(
347        CmsObject cms,
348        CmsSecurityManager securityManager,
349        String resourcename,
350        byte[] content,
351        List<CmsProperty> properties)
352    throws CmsException {
353
354        // initialize a macro resolver with the current user OpenCms context
355        CmsMacroResolver resolver = getMacroResolver(cms, resourcename);
356
357        // add the predefined property values from the XML configuration to the resource
358        List<CmsProperty> newProperties = processDefaultProperties(properties, resolver);
359
360        CmsResource result = securityManager.createResource(
361            cms.getRequestContext(),
362            cms.getRequestContext().addSiteRoot(resourcename),
363            getTypeId(),
364            content,
365            newProperties);
366
367        if ((m_internal != null) && m_internal.booleanValue()) {
368            securityManager.chflags(cms.getRequestContext(), result, result.getFlags() ^ CmsResource.FLAG_INTERNAL);
369        }
370
371        // process the (optional) copy resources from the configuration
372        processCopyResources(cms, resourcename, resolver);
373
374        // create the relations for the new resource
375        createRelations(cms, securityManager, cms.getRequestContext().addSiteRoot(resourcename));
376
377        // return the created resource
378        return result;
379    }
380
381    /**
382     * @see org.opencms.file.types.I_CmsResourceType#createSibling(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, CmsResource, java.lang.String, java.util.List)
383     */
384    public CmsResource createSibling(
385        CmsObject cms,
386        CmsSecurityManager securityManager,
387        CmsResource source,
388        String destination,
389        List<CmsProperty> properties)
390    throws CmsException {
391
392        CmsResource sibling = securityManager.createSibling(
393            cms.getRequestContext(),
394            source,
395            cms.getRequestContext().addSiteRoot(destination),
396            properties);
397        // create the relations for the new resource, this could be improved by an sql query for copying relations
398        createRelations(cms, securityManager, sibling.getRootPath());
399        return sibling;
400    }
401
402    /**
403     * @see org.opencms.file.types.I_CmsResourceType#deleteResource(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceDeleteMode)
404     */
405    public void deleteResource(
406        CmsObject cms,
407        CmsSecurityManager securityManager,
408        CmsResource resource,
409        CmsResource.CmsResourceDeleteMode siblingMode)
410    throws CmsException {
411
412        securityManager.deleteResource(cms.getRequestContext(), resource, siblingMode);
413    }
414
415    /**
416     * Returns <code>true</code>, if this resource type is equal to the given Object.<p>
417     *
418     * Please note: A resource type is identified by it's id {@link #getTypeId()} and it's name {@link #getTypeName()}.
419     * Two resource types are considered equal, if either their id or their name is equal.
420     * This is to prevent issues in the configuration with multiple occurrences of the same name or id.<p>
421     *
422     * @param obj the Object to compare this resource type with
423     *
424     * @return <code>true</code>, if this resource type is equal to the given Object
425     *
426     * @see #getTypeId()
427     * @see #getTypeName()
428     * @see #isIdentical(I_CmsResourceType)
429     * @see java.lang.Object#equals(java.lang.Object)
430     */
431    @Override
432    public boolean equals(Object obj) {
433
434        if (obj == this) {
435            return true;
436        }
437        if (obj instanceof I_CmsResourceType) {
438            I_CmsResourceType other = (I_CmsResourceType)obj;
439            if ((getTypeName() != null) && (getTypeName().equals(other.getTypeName()))) {
440                return true;
441            }
442            if (getTypeId() == other.getTypeId()) {
443                return true;
444            }
445        }
446        return false;
447    }
448
449    /**
450     * @see org.opencms.file.types.I_CmsResourceType#getAdjustLinksFolder()
451     */
452    public String getAdjustLinksFolder() {
453
454        return m_adjustLinksFolder;
455    }
456
457    /**
458     * @see org.opencms.file.types.I_CmsResourceType#getCachePropertyDefault()
459     */
460    public String getCachePropertyDefault() {
461
462        return null;
463    }
464
465    /**
466     * Returns the configured class name of this resource type.<p>
467     *
468     * @see org.opencms.file.types.I_CmsResourceType#getClassName()
469     */
470    public String getClassName() {
471
472        return m_className;
473    }
474
475    /**
476     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration()
477     */
478    public CmsParameterConfiguration getConfiguration() {
479
480        if (LOG.isDebugEnabled()) {
481            LOG.debug(Messages.get().getBundle().key(Messages.LOG_GET_CONFIGURATION_1, this));
482        }
483
484        return m_configuration;
485    }
486
487    /**
488     * Returns the (unmodifiable) list of copy resources.<p>
489     *
490     * @return the (unmodifiable) list of copy resources
491     */
492    public List<CmsConfigurationCopyResource> getConfiguredCopyResources() {
493
494        return m_copyResources;
495    }
496
497    /**
498     * Returns the default properties for this resource type in an unmodifiable List.<p>
499     *
500     * @return the default properties for this resource type in an unmodifiable List
501     */
502    public List<CmsProperty> getConfiguredDefaultProperties() {
503
504        return m_defaultProperties;
505    }
506
507    /**
508     * @see org.opencms.file.types.I_CmsResourceType#getConfiguredMappings()
509     */
510    public List<String> getConfiguredMappings() {
511
512        return m_mappings;
513    }
514
515    /**
516     *
517     * @see org.opencms.file.types.I_CmsResourceType#getFormattersForResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
518     */
519    public CmsFormatterConfiguration getFormattersForResource(CmsObject cms, CmsResource res) {
520
521        return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
522    }
523
524    /**
525     * @see org.opencms.file.types.I_CmsResourceType#getGalleryPreviewProvider()
526     */
527    public String getGalleryPreviewProvider() {
528
529        if (m_galleryPreviewProvider == null) {
530            m_galleryPreviewProvider = getConfiguration().getString(CONFIGURATION_GALLERY_PREVIEW_PROVIDER, null);
531        }
532        return m_galleryPreviewProvider;
533    }
534
535    /**
536     * @see org.opencms.file.types.I_CmsResourceType#getGalleryTypes()
537     */
538    public List<I_CmsResourceType> getGalleryTypes() {
539
540        if (m_galleryTypes == null) {
541            m_galleryTypes = new ArrayList<I_CmsResourceType>();
542            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_galleryTypeNames)) {
543                CmsResourceManager rm = OpenCms.getResourceManager();
544                Iterator<String> iTypeNames = CmsStringUtil.splitAsList(
545                    m_galleryTypeNames,
546                    CmsProperty.VALUE_LIST_DELIMITER).iterator();
547                while (iTypeNames.hasNext()) {
548                    String typeName = iTypeNames.next();
549                    try {
550                        m_galleryTypes.add(rm.getResourceType(typeName));
551                    } catch (CmsLoaderException e) {
552                        if (LOG.isWarnEnabled()) {
553                            LOG.warn(Messages.get().container(Messages.ERR_COULD_NOT_READ_RESOURCE_TYPE_1, typeName));
554                        }
555                    }
556                }
557            }
558
559        }
560        return m_galleryTypes;
561    }
562
563    /**
564     * @see org.opencms.file.types.I_CmsResourceType#getLoaderId()
565     */
566    public abstract int getLoaderId();
567
568    /**
569     * @see org.opencms.file.types.I_CmsResourceType#getModuleName()
570     */
571    public String getModuleName() {
572
573        return m_moduleName;
574    }
575
576    /**
577     * @see org.opencms.file.types.I_CmsResourceType#getTypeId()
578     *
579     * @deprecated
580     * Use this class or {@link #getTypeName()} instead.
581     * Resource types should always be referenced either by its type class (preferred) or by type name.
582     * Use of int based resource type references will be discontinued in a future OpenCms release.
583     */
584    @Deprecated
585    public int getTypeId() {
586
587        return m_typeId;
588    }
589
590    /**
591     * @see org.opencms.file.types.I_CmsResourceType#getTypeName()
592     */
593    public String getTypeName() {
594
595        return m_typeName;
596    }
597
598    /**
599     * The hash code implementation uses the type name to generate a hash code.<p>
600     *
601     * @see #getTypeId()
602     * @see java.lang.Object#hashCode()
603     */
604    @Override
605    public int hashCode() {
606
607        return getTypeName().hashCode();
608    }
609
610    /**
611     * @see org.opencms.file.types.I_CmsResourceType#importResource(org.opencms.file.CmsObject, CmsSecurityManager, java.lang.String, org.opencms.file.CmsResource, byte[], List)
612     */
613    public CmsResource importResource(
614        CmsObject cms,
615        CmsSecurityManager securityManager,
616        String resourcename,
617        CmsResource resource,
618        byte[] content,
619        List<CmsProperty> properties)
620    throws CmsException {
621
622        // this triggers the internal "is touched" state
623        // and prevents the security manager from inserting the current time
624        resource.setDateLastModified(resource.getDateLastModified());
625        // ensure resource record is updated
626        resource.setState(CmsResource.STATE_NEW);
627        return securityManager.importResource(
628            cms.getRequestContext(),
629            cms.getRequestContext().addSiteRoot(resourcename),
630            resource,
631            content,
632            properties,
633            true);
634    }
635
636    /**
637     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration()
638     */
639    public final void initConfiguration() {
640
641        // final since subclasses should NOT implement this, but rather the version with 3 String parameters (see below)
642        if (LOG.isDebugEnabled()) {
643            LOG.debug(Messages.get().getBundle().key(Messages.LOG_INIT_CONFIGURATION_1, this));
644        }
645    }
646
647    /**
648     * @see org.opencms.file.types.I_CmsResourceType#initConfiguration(java.lang.String, java.lang.String, java.lang.String)
649     */
650    public void initConfiguration(String name, String id, String className) throws CmsConfigurationException {
651
652        if (LOG.isDebugEnabled()) {
653            LOG.debug(Messages.get().getBundle().key(Messages.LOG_INIT_CONFIGURATION_3, this, name, id));
654        }
655
656        if (m_frozen) {
657            // configuration already frozen
658            throw new CmsConfigurationException(
659                org.opencms.configuration.Messages.get().container(
660                    org.opencms.file.types.Messages.ERR_CONFIG_FROZEN_3,
661                    className,
662                    getTypeName(),
663                    new Integer(getTypeId())));
664        }
665
666        // freeze the configuration
667        m_frozen = true;
668
669        // set type name and id (please note that some resource types have a fixed type / id)
670        if (name != null) {
671            m_typeName = name;
672        }
673        if (id != null) {
674            m_typeId = Integer.valueOf(id).intValue();
675        }
676        if (className != null) {
677            m_className = className;
678        }
679
680        // check type id, type name and class name
681        if ((getTypeName() == null)
682            || (getClassName() == null)
683            || ((getTypeId() < 0)
684                && (!m_typeName.equals(CmsResourceTypeUnknownFile.getStaticTypeName()))
685                && (!m_typeName.equals(CmsResourceTypeUnknownFolder.getStaticTypeName())))) {
686            throw new CmsConfigurationException(
687                Messages.get().container(
688                    Messages.ERR_INVALID_RESTYPE_CONFIG_3,
689                    className,
690                    m_typeName,
691                    new Integer(m_typeId)));
692        }
693
694        m_defaultProperties = Collections.unmodifiableList(m_defaultProperties);
695        m_copyResources = Collections.unmodifiableList(m_copyResources);
696        m_mappings = Collections.unmodifiableList(m_mappings);
697        m_configuration = CmsParameterConfiguration.unmodifiableVersion(m_configuration);
698    }
699
700    /**
701     * @see org.opencms.file.types.I_CmsResourceType#initialize(org.opencms.file.CmsObject)
702     */
703    public void initialize(CmsObject cms) {
704
705        // most resource type do not require any runtime information
706        if (LOG.isDebugEnabled()) {
707            LOG.debug(Messages.get().getBundle().key(Messages.LOG_INITIALIZE_1, this));
708        }
709    }
710
711    /**
712     * @see org.opencms.file.types.I_CmsResourceType#isAdditionalModuleResourceType()
713     */
714    public boolean isAdditionalModuleResourceType() {
715
716        return m_addititionalModuleResourceType;
717    }
718
719    /**
720     * @see org.opencms.file.types.I_CmsResourceType#isDirectEditable()
721     */
722    public boolean isDirectEditable() {
723
724        return false;
725    }
726
727    /**
728     * @see org.opencms.file.types.I_CmsResourceType#isFolder()
729     */
730    public boolean isFolder() {
731
732        return false;
733    }
734
735    /**
736     * @see org.opencms.file.types.I_CmsResourceType#isIdentical(org.opencms.file.types.I_CmsResourceType)
737     */
738    public boolean isIdentical(I_CmsResourceType type) {
739
740        if (type == null) {
741            return false;
742        }
743
744        return (getTypeId() == type.getTypeId()) && (getTypeName().equals(type.getTypeName()));
745    }
746
747    /**
748     * @see org.opencms.file.types.I_CmsResourceType#lockResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, org.opencms.lock.CmsLockType)
749     */
750    public void lockResource(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, CmsLockType type)
751    throws CmsException {
752
753        securityManager.lockResource(cms.getRequestContext(), resource, type);
754    }
755
756    /**
757     * @see org.opencms.file.types.I_CmsResourceType#moveResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, java.lang.String)
758     */
759    public void moveResource(
760        CmsObject cms,
761        CmsSecurityManager securityManager,
762        CmsResource resource,
763        String destination)
764    throws CmsException, CmsIllegalArgumentException {
765
766        String dest = cms.getRequestContext().addSiteRoot(destination);
767        if (resource.getRootPath().equals(dest)) {
768            // move to target with same name is not allowed
769            throw new CmsVfsException(
770                org.opencms.file.Messages.get().container(org.opencms.file.Messages.ERR_MOVE_SAME_NAME_1, destination));
771        }
772        // check the destination
773        try {
774            securityManager.readResource(cms.getRequestContext(), dest, CmsResourceFilter.ALL);
775            throw new CmsVfsException(
776                org.opencms.file.Messages.get().container(
777                    org.opencms.file.Messages.ERR_OVERWRITE_RESOURCE_2,
778                    cms.getRequestContext().removeSiteRoot(resource.getRootPath()),
779                    destination));
780        } catch (CmsVfsResourceNotFoundException e) {
781            // ok
782        }
783        String targetName = CmsResource.getName(destination).replace("/", "");
784        CmsResource.checkResourceName(targetName);
785        // move
786        securityManager.moveResource(cms.getRequestContext(), resource, dest);
787    }
788
789    /**
790     * @see org.opencms.file.types.I_CmsResourceType#removeResourceFromProject(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource)
791     */
792    public void removeResourceFromProject(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource)
793    throws CmsException {
794
795        securityManager.removeResourceFromProject(cms.getRequestContext(), resource);
796    }
797
798    /**
799     * @see org.opencms.file.types.I_CmsResourceType#replaceResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, org.opencms.file.types.I_CmsResourceType, byte[], java.util.List)
800     */
801    public void replaceResource(
802        CmsObject cms,
803        CmsSecurityManager securityManager,
804        CmsResource resource,
805        I_CmsResourceType type,
806        byte[] content,
807        List<CmsProperty> properties)
808    throws CmsException {
809
810        // TODO: Refactor driver layer to use resource type id classes (or names) instead of int
811        replaceResource(cms, securityManager, resource, type.getTypeId(), content, properties);
812    }
813
814    /**
815     * @see org.opencms.file.types.I_CmsResourceType#replaceResource(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
816     *
817     * @deprecated
818     * Use {@link #replaceResource(CmsObject, CmsSecurityManager, CmsResource, I_CmsResourceType, byte[], List)} instead.
819     * Resource types should always be referenced either by its type class (preferred) or by type name.
820     * Use of int based resource type references will be discontinued in a future OpenCms release.
821     */
822    @Deprecated
823    public void replaceResource(
824        CmsObject cms,
825        CmsSecurityManager securityManager,
826        CmsResource resource,
827        int type,
828        byte[] content,
829        List<CmsProperty> properties)
830    throws CmsException {
831
832        securityManager.replaceResource(cms.getRequestContext(), resource, type, content, properties);
833        // type may have changed from non link parseable to link parseable
834        createRelations(cms, securityManager, resource.getRootPath());
835    }
836
837    /**
838     * @see org.opencms.file.types.I_CmsResourceType#restoreResource(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, int)
839     */
840    public void restoreResource(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, int version)
841    throws CmsException {
842
843        securityManager.restoreResource(cms.getRequestContext(), resource, version);
844        // type may have changed from non link parseable to link parseable
845        createRelations(cms, securityManager, resource.getRootPath());
846    }
847
848    /**
849     * @see org.opencms.file.types.I_CmsResourceType#setAdditionalModuleResourceType(boolean)
850     */
851    public void setAdditionalModuleResourceType(boolean additionalType) {
852
853        m_addititionalModuleResourceType = additionalType;
854    }
855
856    /**
857     * @see org.opencms.file.types.I_CmsResourceType#setAdjustLinksFolder(String)
858     */
859    public void setAdjustLinksFolder(String adjustLinksFolder) {
860
861        m_adjustLinksFolder = adjustLinksFolder;
862    }
863
864    /**
865     * @see org.opencms.file.types.I_CmsResourceType#setDateExpired(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, long, boolean)
866     */
867    public void setDateExpired(
868        CmsObject cms,
869        CmsSecurityManager securityManager,
870        CmsResource resource,
871        long dateExpired,
872        boolean recursive)
873    throws CmsException {
874
875        securityManager.setDateExpired(cms.getRequestContext(), resource, dateExpired);
876    }
877
878    /**
879     * @see org.opencms.file.types.I_CmsResourceType#setDateLastModified(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, long, boolean)
880     */
881    public void setDateLastModified(
882        CmsObject cms,
883        CmsSecurityManager securityManager,
884        CmsResource resource,
885        long dateLastModified,
886        boolean recursive)
887    throws CmsException {
888
889        securityManager.setDateLastModified(cms.getRequestContext(), resource, dateLastModified);
890    }
891
892    /**
893     * @see org.opencms.file.types.I_CmsResourceType#setDateReleased(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, long, boolean)
894     */
895    public void setDateReleased(
896        CmsObject cms,
897        CmsSecurityManager securityManager,
898        CmsResource resource,
899        long dateReleased,
900        boolean recursive)
901    throws CmsException {
902
903        securityManager.setDateReleased(cms.getRequestContext(), resource, dateReleased);
904    }
905
906    /**
907     * @see org.opencms.file.types.I_CmsResourceType#setModuleName(java.lang.String)
908     */
909    public void setModuleName(String moduleName) {
910
911        m_moduleName = moduleName;
912    }
913
914    /**
915     * @see java.lang.Object#toString()
916     */
917    @Override
918    public String toString() {
919
920        StringBuffer output = new StringBuffer();
921        output.append("[ResourceType] class=");
922        output.append(getClass().getName());
923        output.append(" name=");
924        output.append(getTypeName());
925        output.append(" id=");
926        output.append(getTypeId());
927        output.append(" loaderId=");
928        output.append(getLoaderId());
929        return output.toString();
930    }
931
932    /**
933     * @see org.opencms.file.types.I_CmsResourceType#undelete(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, boolean)
934     */
935    public void undelete(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, boolean recursive)
936    throws CmsException {
937
938        securityManager.undelete(cms.getRequestContext(), resource);
939    }
940
941    /**
942     * @see org.opencms.file.types.I_CmsResourceType#undoChanges(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
943     */
944    public void undoChanges(
945        CmsObject cms,
946        CmsSecurityManager securityManager,
947        CmsResource resource,
948        CmsResource.CmsResourceUndoMode mode)
949    throws CmsException {
950
951        securityManager.undoChanges(cms.getRequestContext(), resource, mode);
952        updateRelationForUndo(cms, securityManager, resource);
953    }
954
955    /**
956     * @see org.opencms.file.types.I_CmsResourceType#unlockResource(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource)
957     */
958    public void unlockResource(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource)
959    throws CmsException {
960
961        securityManager.unlockResource(cms.getRequestContext(), resource);
962    }
963
964    /**
965     * @see org.opencms.file.types.I_CmsResourceType#writeFile(org.opencms.file.CmsObject, CmsSecurityManager, CmsFile)
966     */
967    public CmsFile writeFile(CmsObject cms, CmsSecurityManager securityManager, CmsFile resource) throws CmsException {
968
969        if (resource.isFile()) {
970            CmsFile file = securityManager.writeFile(cms.getRequestContext(), resource);
971            I_CmsResourceType type = getResourceType(file);
972            // update the relations after writing!!
973            List<CmsLink> links = null;
974            if (type instanceof I_CmsLinkParseable) { // this check is needed because of type change
975                // if the new type is link parseable
976                links = ((I_CmsLinkParseable)type).parseLinks(cms, file);
977            }
978            // this has to be always executed, even if not link parseable to remove old links
979            securityManager.updateRelationsForResource(cms.getRequestContext(), file, links);
980            return file;
981        }
982        // folders can never be written like a file
983        throw new CmsVfsException(
984            Messages.get().container(Messages.ERR_WRITE_FILE_IS_FOLDER_1, cms.getSitePath(resource)));
985    }
986
987    /**
988     * @see org.opencms.file.types.I_CmsResourceType#writePropertyObject(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, CmsResource, org.opencms.file.CmsProperty)
989     */
990    public void writePropertyObject(
991        CmsObject cms,
992        CmsSecurityManager securityManager,
993        CmsResource resource,
994        CmsProperty property)
995    throws CmsException {
996
997        securityManager.writePropertyObject(cms.getRequestContext(), resource, property);
998    }
999
1000    /**
1001     * @see org.opencms.file.types.I_CmsResourceType#writePropertyObjects(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, CmsResource, java.util.List)
1002     */
1003    public void writePropertyObjects(
1004        CmsObject cms,
1005        CmsSecurityManager securityManager,
1006        CmsResource resource,
1007        List<CmsProperty> properties)
1008    throws CmsException {
1009
1010        securityManager.writePropertyObjects(cms.getRequestContext(), resource, properties);
1011    }
1012
1013    /**
1014     * Creates the relation information for the resource with the given resource name.<p>
1015     *
1016     * @param cms the cms context
1017     * @param securityManager the security manager
1018     * @param resourceName the resource name of the resource to update the relations for
1019     *
1020     * @return the fresh read resource
1021     *
1022     * @throws CmsException if something goes wrong
1023     */
1024    protected CmsResource createRelations(CmsObject cms, CmsSecurityManager securityManager, String resourceName)
1025    throws CmsException {
1026
1027        CmsResource resource = securityManager.readResource(
1028            cms.getRequestContext(),
1029            resourceName,
1030            CmsResourceFilter.ALL);
1031        I_CmsResourceType resourceType = getResourceType(resource);
1032        List<CmsLink> links = null;
1033        if (resourceType instanceof I_CmsLinkParseable) {
1034            I_CmsLinkParseable linkParseable = (I_CmsLinkParseable)resourceType;
1035            links = linkParseable.parseLinks(cms, cms.readFile(resource));
1036        }
1037        securityManager.updateRelationsForResource(cms.getRequestContext(), resource, links);
1038        return resource;
1039    }
1040
1041    /**
1042     * Creates a macro resolver based on the current users OpenCms context and the provided resource name.<p>
1043     *
1044     * @param cms the current OpenCms user context
1045     * @param resourcename the resource name for macros like {@link A_CmsResourceType#MACRO_RESOURCE_FOLDER_PATH}
1046     *
1047     * @return a macro resolver based on the current users OpenCms context and the provided resource name
1048     */
1049    protected CmsMacroResolver getMacroResolver(CmsObject cms, String resourcename) {
1050
1051        CmsMacroResolver result = CmsMacroResolver.newInstance().setCmsObject(cms);
1052        if (isFolder() && (!CmsResource.isFolder(resourcename))) {
1053            // ensure folder ends with "/" so
1054            resourcename = resourcename.concat("/");
1055        }
1056        // add special mappings for macros in default properties
1057        result.addMacro(MACRO_RESOURCE_ROOT_PATH, cms.getRequestContext().addSiteRoot(resourcename));
1058        result.addMacro(MACRO_RESOURCE_SITE_PATH, resourcename);
1059        result.addMacro(MACRO_RESOURCE_FOLDER_PATH, CmsResource.getFolderPath(resourcename));
1060        result.addMacro(MACRO_RESOURCE_FOLDER_PATH_TOUCH, CmsResource.getFolderPath(resourcename));
1061        result.addMacro(MACRO_RESOURCE_PARENT_PATH, CmsResource.getParentFolder(resourcename));
1062        result.addMacro(MACRO_RESOURCE_NAME, CmsResource.getName(resourcename));
1063
1064        return result;
1065    }
1066
1067    /**
1068     * Convenience method to get the initialized resource type instance for the given resource,
1069     * with a fall back to special "unknown" resource types in case the resource type is not configured.<p>
1070     *
1071     * @param resource the resource to get the type for
1072     *
1073     * @return the initialized resource type instance for the given resource
1074     *
1075     * @see org.opencms.loader.CmsResourceManager#getResourceType(int)
1076     */
1077    protected I_CmsResourceType getResourceType(CmsResource resource) {
1078
1079        return OpenCms.getResourceManager().getResourceType(resource);
1080    }
1081
1082    /**
1083     * Processes the copy resources of this resource type.<p>
1084     *
1085     * @param cms the current OpenCms user context
1086     * @param resourcename the name of the base resource
1087     * @param resolver the resolver used for resolving target macro names
1088     */
1089    protected void processCopyResources(CmsObject cms, String resourcename, CmsMacroResolver resolver) {
1090
1091        Map<String, String> copiedResources = new HashMap<String, String>();
1092        for (CmsConfigurationCopyResource oriCopyResource : m_copyResources) {
1093
1094            // store original copy target
1095            String oriTarget = oriCopyResource.getTarget();
1096            String target = oriTarget;
1097
1098            List<CmsConfigurationCopyResource> copyResources = new ArrayList<CmsConfigurationCopyResource>();
1099            try {
1100                // determine if source definition has a wild card character at the end
1101                if (oriCopyResource.getSource().endsWith("*")) {
1102                    // add all sub resources of the specified source folder to the set of resources to copy
1103                    String source = oriCopyResource.getSource().substring(0, oriCopyResource.getSource().length() - 1);
1104                    List<CmsResource> sources = cms.readResources(source, CmsResourceFilter.IGNORE_EXPIRATION, false);
1105                    for (CmsResource sourceRes : sources) {
1106                        copyResources.add(
1107                            new CmsConfigurationCopyResource(
1108                                cms.getSitePath(sourceRes),
1109                                oriCopyResource.getTarget(),
1110                                oriCopyResource.getTypeString()));
1111                    }
1112                    copiedResources.put(source, resolver.resolveMacros(target));
1113                } else {
1114                    // just add the single specified source
1115                    copyResources.add(oriCopyResource);
1116                }
1117
1118                // loop the calculated resources to copy
1119                for (CmsConfigurationCopyResource copyResource : copyResources) {
1120
1121                    target = copyResource.getTarget();
1122                    if (copyResource.isTargetWasNull()
1123                        || CmsMacroResolver.isMacro(target, MACRO_RESOURCE_FOLDER_PATH)
1124                        || CmsMacroResolver.isMacro(target, MACRO_RESOURCE_FOLDER_PATH_TOUCH)) {
1125                        // target is just the resource folder, must add source file name to target
1126                        target = target.concat(CmsResource.getName(copyResource.getSource()));
1127                    }
1128                    // now resolve the macros in the target name
1129                    target = resolver.resolveMacros(target);
1130                    // now resolve possible relative paths in the target
1131                    target = CmsFileUtil.normalizePath(CmsLinkManager.getAbsoluteUri(target, resourcename), '/');
1132
1133                    // copy the resource
1134                    cms.copyResource(copyResource.getSource(), target, copyResource.getType());
1135                    copiedResources.put(copyResource.getSource(), target);
1136                    if (CmsMacroResolver.isMacro(oriTarget, MACRO_RESOURCE_FOLDER_PATH_TOUCH)) {
1137                        // copied resources should be touched in order to be able to do additional stuff
1138                        CmsResource res = cms.readResource(target);
1139                        if (res.isFile()) {
1140                            // single file, just rewrite it
1141                            CmsFile file = cms.readFile(res);
1142                            cms.writeFile(file);
1143                        } else {
1144                            // folder, get all sub resources that are files
1145                            Iterator<CmsResource> it = cms.readResources(
1146                                target,
1147                                CmsResourceFilter.DEFAULT_FILES,
1148                                true).iterator();
1149                            while (it.hasNext()) {
1150                                // rewrite the sub resource
1151                                CmsResource subRes = it.next();
1152                                CmsFile file = cms.readFile(subRes);
1153                                cms.writeFile(file);
1154                            }
1155                        }
1156                    }
1157
1158                }
1159            } catch (Exception e) {
1160                // CmsIllegalArgumentException as well as CmsException
1161                // log the error and continue with the other copy resources
1162                if (LOG.isDebugEnabled()) {
1163                    // log stack trace in debug level only
1164                    LOG.debug(
1165                        Messages.get().getBundle().key(
1166                            Messages.LOG_PROCESS_COPY_RESOURCES_3,
1167                            resourcename,
1168                            oriCopyResource,
1169                            target),
1170                        e);
1171                } else {
1172                    LOG.error(
1173                        Messages.get().getBundle().key(
1174                            Messages.LOG_PROCESS_COPY_RESOURCES_3,
1175                            resourcename,
1176                            oriCopyResource,
1177                            target));
1178                }
1179            }
1180        }
1181        // only adjust links for successfully copied resources and if the feature is enabled
1182        try {
1183            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_adjustLinksFolder) && !copiedResources.isEmpty()) {
1184                String realAdjustFolderPath = resolver.resolveMacros(m_adjustLinksFolder);
1185                cms.adjustLinks(copiedResources, realAdjustFolderPath);
1186            }
1187        } catch (CmsException e) {
1188            LOG.error(e.getLocalizedMessage(), e);
1189        } catch (CmsIllegalArgumentException e) {
1190            LOG.error(e.getLocalizedMessage(), e);
1191        }
1192    }
1193
1194    /**
1195     * Returns a list of property objects that are attached to the resource on creation.<p>
1196     *
1197     * It's possible to use OpenCms macros for the property values.
1198     * Please see {@link CmsMacroResolver} for allowed macro values.<p>
1199     *
1200     * @param properties the (optional) properties provided by the user
1201     * @param resolver the resolver used to resolve the macro values
1202     *
1203     * @return a list of property objects that are attached to the resource on creation
1204     */
1205    protected List<CmsProperty> processDefaultProperties(List<CmsProperty> properties, CmsMacroResolver resolver) {
1206
1207        if ((m_defaultProperties == null) || (m_defaultProperties.size() == 0)) {
1208            // no default properties are defined
1209            return properties;
1210        }
1211
1212        // the properties must be copied since the macros could contain macros that are
1213        // resolved differently for every user / context
1214        ArrayList<CmsProperty> result = new ArrayList<CmsProperty>();
1215        Iterator<CmsProperty> i = m_defaultProperties.iterator();
1216
1217        while (i.hasNext()) {
1218            // create a clone of the next property
1219            CmsProperty property = (i.next()).clone();
1220
1221            // resolve possible macros in the property values
1222            if (property.getResourceValue() != null) {
1223                property.setResourceValue(resolver.resolveMacros(property.getResourceValue()));
1224            }
1225            if (property.getStructureValue() != null) {
1226                property.setStructureValue(resolver.resolveMacros(property.getStructureValue()));
1227            }
1228
1229            // save the new property in the result list
1230            result.add(property);
1231        }
1232
1233        // add the original properties
1234        if (properties != null) {
1235            result.addAll(properties);
1236        }
1237
1238        // return the result
1239        return result;
1240    }
1241
1242    /**
1243     * Update the relations after an undo changes operation.<p>
1244     *
1245     * @param cms the cms context
1246     * @param securityManager the security manager
1247     * @param resource the resource that has been undone
1248     *
1249     * @throws CmsException if something goes wrong
1250     */
1251    protected void updateRelationForUndo(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource)
1252    throws CmsException {
1253
1254        // type may have changed from non link parseable to link parseable
1255        CmsResource undoneResource1 = null;
1256        try {
1257            // first try to locate the resource by path
1258            undoneResource1 = createRelations(cms, securityManager, resource.getRootPath());
1259        } catch (CmsVfsResourceNotFoundException e) {
1260            // ignore, undone move operation
1261        }
1262        // now, in case a move operation has been undone, locate the resource by id
1263        CmsResource undoneResource2 = securityManager.readResource(
1264            cms.getRequestContext(),
1265            resource.getStructureId(),
1266            CmsResourceFilter.ALL);
1267        if (!undoneResource2.equals(undoneResource1)) {
1268            I_CmsResourceType resourceType = getResourceType(resource);
1269            List<CmsLink> links = null;
1270            if (resourceType instanceof I_CmsLinkParseable) {
1271                I_CmsLinkParseable linkParseable = (I_CmsLinkParseable)resourceType;
1272                if ((undoneResource1 == null) || !undoneResource2.getRootPath().equals(undoneResource1.getRootPath())) {
1273                    try {
1274                        links = linkParseable.parseLinks(cms, cms.readFile(undoneResource2));
1275                    } catch (CmsException e) {
1276                        if (LOG.isWarnEnabled()) {
1277                            LOG.warn(e);
1278                        }
1279                    } catch (CmsRuntimeException e) {
1280                        if (LOG.isWarnEnabled()) {
1281                            LOG.warn(e);
1282                        }
1283                    }
1284                }
1285            }
1286            securityManager.updateRelationsForResource(cms.getRequestContext(), undoneResource2, links);
1287        }
1288    }
1289}