001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (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, 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.loader;
029
030import org.opencms.cache.CmsVfsMemoryObjectCache;
031import org.opencms.configuration.CmsConfigurationException;
032import org.opencms.configuration.CmsVfsConfiguration;
033import org.opencms.file.CmsObject;
034import org.opencms.file.CmsProperty;
035import org.opencms.file.CmsPropertyDefinition;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsResourceFilter;
038import org.opencms.file.collectors.I_CmsResourceCollector;
039import org.opencms.file.types.CmsResourceTypeBinary;
040import org.opencms.file.types.CmsResourceTypeFolder;
041import org.opencms.file.types.CmsResourceTypePlain;
042import org.opencms.file.types.CmsResourceTypeUnknownFile;
043import org.opencms.file.types.CmsResourceTypeUnknownFolder;
044import org.opencms.file.types.CmsResourceTypeXmlContent;
045import org.opencms.file.types.I_CmsResourceType;
046import org.opencms.main.CmsException;
047import org.opencms.main.CmsLog;
048import org.opencms.main.OpenCms;
049import org.opencms.module.CmsModule;
050import org.opencms.module.CmsModuleManager;
051import org.opencms.relations.CmsRelationType;
052import org.opencms.security.CmsRole;
053import org.opencms.security.CmsRoleViolationException;
054import org.opencms.util.CmsDefaultSet;
055import org.opencms.util.CmsHtmlConverter;
056import org.opencms.util.CmsHtmlConverterJTidy;
057import org.opencms.util.CmsHtmlConverterOption;
058import org.opencms.util.CmsResourceTranslator;
059import org.opencms.util.CmsStringUtil;
060import org.opencms.util.I_CmsHtmlConverter;
061import org.opencms.workplace.CmsWorkplace;
062import org.opencms.xml.CmsXmlContentDefinition;
063
064import java.io.IOException;
065import java.util.ArrayList;
066import java.util.Collections;
067import java.util.HashMap;
068import java.util.Iterator;
069import java.util.List;
070import java.util.Locale;
071import java.util.Map;
072import java.util.Properties;
073
074import javax.servlet.ServletException;
075import javax.servlet.http.HttpServletRequest;
076import javax.servlet.http.HttpServletResponse;
077
078import org.apache.commons.logging.Log;
079
080/**
081 * Collects all available resource loaders, resource types and resource collectors at startup and provides
082 * methods to access them during OpenCms runtime.<p>
083 *
084 * @since 6.0.0
085 */
086public class CmsResourceManager {
087
088    /**
089     * Bean containing a template resource and the name of the template.<p>
090     */
091    public static class NamedTemplate {
092
093        /** The template name. */
094        private String m_name;
095
096        /** The template resource. */
097        private CmsResource m_resource;
098
099        /**
100         * Creates a new instance.<p>
101         *
102         * @param resource the template resource
103         * @param name the template name
104         */
105        public NamedTemplate(CmsResource resource, String name) {
106
107            m_resource = resource;
108            m_name = name;
109        }
110
111        /**
112         * Gets the template name.<p>
113         *
114         * @return the template name
115         */
116        public String getName() {
117
118            return m_name;
119        }
120
121        /**
122         * Gets the template resource.<p>
123         *
124         * @return the template resource
125         */
126        public CmsResource getResource() {
127
128            return m_resource;
129        }
130    }
131
132    /**
133     * Contains the part of the resource manager configuration that can be changed
134     * during runtime by the import / deletion of a module.<p>
135     *
136     * A module can add resource types and extension mappings to resource types.<p>
137     */
138    static final class CmsResourceManagerConfiguration {
139
140        /** The mappings of file extensions to resource types. */
141        protected Map<String, String> m_extensionMappings;
142
143        /** A list that contains all initialized resource types. */
144        protected List<I_CmsResourceType> m_resourceTypeList;
145
146        /** A list that contains all initialized resource types, plus configured types for "unknown" resources. */
147        protected List<I_CmsResourceType> m_resourceTypeListWithUnknown;
148
149        /** A map that contains all initialized resource types mapped to their type id. */
150        private Map<Integer, I_CmsResourceType> m_resourceTypeIdMap;
151
152        /** A map that contains all initialized resource types mapped to their type name. */
153        private Map<String, I_CmsResourceType> m_resourceTypeNameMap;
154
155        /**
156         * Creates a new resource manager data storage.<p>
157         */
158        protected CmsResourceManagerConfiguration() {
159
160            m_resourceTypeIdMap = new HashMap<Integer, I_CmsResourceType>(128);
161            m_resourceTypeNameMap = new HashMap<String, I_CmsResourceType>(128);
162            m_extensionMappings = new HashMap<String, String>(128);
163            m_resourceTypeList = new ArrayList<I_CmsResourceType>(32);
164        }
165
166        /**
167         * Adds a resource type to the list of configured resource types.<p>
168         *
169         * @param type the resource type to add
170         */
171        protected void addResourceType(I_CmsResourceType type) {
172
173            m_resourceTypeIdMap.put(Integer.valueOf(type.getTypeId()), type);
174            m_resourceTypeNameMap.put(type.getTypeName(), type);
175            m_resourceTypeList.add(type);
176        }
177
178        /**
179         * Freezes the current configuration by making all data structures unmodifiable
180         * that can be accessed form outside this class.<p>
181         *
182         * @param restypeUnknownFolder the configured default resource type for unknown folders
183         * @param restypeUnknownFile the configured default resource type for unknown files
184         */
185        protected void freeze(I_CmsResourceType restypeUnknownFolder, I_CmsResourceType restypeUnknownFile) {
186
187            // generate the resource type list with unknown resource types
188            m_resourceTypeListWithUnknown = new ArrayList<I_CmsResourceType>(m_resourceTypeList.size() + 2);
189            if (restypeUnknownFolder != null) {
190                m_resourceTypeListWithUnknown.add(restypeUnknownFolder);
191            }
192            if (restypeUnknownFile != null) {
193                m_resourceTypeListWithUnknown.add(restypeUnknownFile);
194            }
195            m_resourceTypeListWithUnknown.addAll(m_resourceTypeList);
196
197            // freeze the current configuration
198            m_resourceTypeListWithUnknown = Collections.unmodifiableList(m_resourceTypeListWithUnknown);
199            m_resourceTypeList = Collections.unmodifiableList(m_resourceTypeList);
200            m_extensionMappings = Collections.unmodifiableMap(m_extensionMappings);
201        }
202
203        /**
204         * Returns the configured resource type with the matching type id, or <code>null</code>
205         * if a resource type with that id is not configured.<p>
206         *
207         * @param typeId the type id to get the resource type for
208         *
209         * @return the configured resource type with the matching type id, or <code>null</code>
210         */
211        protected I_CmsResourceType getResourceTypeById(int typeId) {
212
213            return m_resourceTypeIdMap.get(Integer.valueOf(typeId));
214        }
215
216        /**
217         * Returns the configured resource type with the matching type name, or <code>null</code>
218         * if a resource type with that name is not configured.<p>
219         *
220         * @param typeName the type name to get the resource type for
221         *
222         * @return the configured resource type with the matching type name, or <code>null</code>
223         */
224        protected I_CmsResourceType getResourceTypeByName(String typeName) {
225
226            return m_resourceTypeNameMap.get(typeName);
227        }
228    }
229
230    /** The path to the default template. */
231    public static final String DEFAULT_TEMPLATE = CmsWorkplace.VFS_PATH_COMMONS + "template/default.jsp";
232
233    /** The MIME type <code>"text/html"</code>. */
234    public static final String MIMETYPE_HTML = "text/html";
235
236    /** The MIME type <code>"text/plain"</code>. */
237    public static final String MIMETYPE_TEXT = "text/plain";
238
239    /** The log object for this class. */
240    private static final Log LOG = CmsLog.getLog(CmsResourceManager.class);
241
242    /** The map for all configured collector names, mapped to their collector class. */
243    private Map<String, I_CmsResourceCollector> m_collectorNameMappings;
244
245    /** The list of all currently configured content collector instances. */
246    private List<I_CmsResourceCollector> m_collectors;
247
248    /** The current resource manager configuration. */
249    private CmsResourceManagerConfiguration m_configuration;
250
251    /** The list of all configured HTML converters. */
252    private List<CmsHtmlConverterOption> m_configuredHtmlConverters;
253
254    /** The list of all configured MIME types. */
255    private List<CmsMimeType> m_configuredMimeTypes;
256
257    /** The list of all configured relation types. */
258    private List<CmsRelationType> m_configuredRelationTypes;
259
260    /** Filename translator, used only for the creation of new files. */
261    private CmsResourceTranslator m_fileTranslator;
262
263    /** Folder translator, used to translate all accesses to resources. */
264    private CmsResourceTranslator m_folderTranslator;
265
266    /** Indicates if the configuration is finalized (frozen). */
267    private boolean m_frozen;
268
269    /** The OpenCms map of configured HTML converters. */
270    private Map<String, String> m_htmlConverters;
271
272    /** A list that contains all initialized resource loaders. */
273    private List<I_CmsResourceLoader> m_loaderList;
274
275    /** All initialized resource loaders, mapped to their id. */
276    private I_CmsResourceLoader[] m_loaders;
277
278    /** The OpenCms map of configured MIME types. */
279    private Map<String, String> m_mimeTypes;
280
281    /** The URL name generator for XML contents. */
282    private I_CmsFileNameGenerator m_nameGenerator;
283
284    /** A list that contains all resource types added from the XML configuration. */
285    private List<I_CmsResourceType> m_resourceTypesFromXml;
286
287    /** The configured default type for files when the resource type is missing. */
288    private I_CmsResourceType m_restypeUnknownFile;
289
290    /** The configured default type for folders when the resource type is missing. */
291    private I_CmsResourceType m_restypeUnknownFolder;
292
293    /** Cache for template names. */
294    private CmsVfsMemoryObjectCache m_templateNameCache = new CmsVfsMemoryObjectCache();
295
296    /** XSD translator, used to translate all accesses to XML schemas from Strings. */
297    private CmsResourceTranslator m_xsdTranslator;
298
299    /**
300     * Creates a new instance for the resource manager,
301     * will be called by the VFS configuration manager.<p>
302     */
303    public CmsResourceManager() {
304
305        if (CmsLog.INIT.isInfoEnabled()) {
306            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STARTING_LOADER_CONFIG_0));
307        }
308
309        m_resourceTypesFromXml = new ArrayList<I_CmsResourceType>();
310        m_loaders = new I_CmsResourceLoader[16];
311        m_loaderList = new ArrayList<I_CmsResourceLoader>();
312        m_configuredMimeTypes = new ArrayList<CmsMimeType>();
313        m_configuredRelationTypes = new ArrayList<CmsRelationType>();
314        m_configuredHtmlConverters = new ArrayList<CmsHtmlConverterOption>();
315    }
316
317    /**
318     * Adds a given content collector class to the type manager.<p>
319     *
320     * @param className the name of the class to add
321     * @param order the order number for this collector
322     *
323     * @return the created content collector instance
324     *
325     * @throws CmsConfigurationException in case the collector could not be properly initialized
326     */
327    public synchronized I_CmsResourceCollector addContentCollector(String className, String order)
328    throws CmsConfigurationException {
329
330        Class<?> classClazz;
331        // init class for content collector
332        try {
333            classClazz = Class.forName(className);
334        } catch (ClassNotFoundException e) {
335            LOG.error(Messages.get().getBundle().key(Messages.LOG_CONTENT_COLLECTOR_CLASS_NOT_FOUND_1, className), e);
336            return null;
337        }
338
339        I_CmsResourceCollector collector;
340        try {
341            collector = (I_CmsResourceCollector)classClazz.newInstance();
342        } catch (InstantiationException e) {
343            throw new CmsConfigurationException(
344                Messages.get().container(Messages.ERR_INVALID_COLLECTOR_NAME_1, className));
345        } catch (IllegalAccessException e) {
346            throw new CmsConfigurationException(
347                Messages.get().container(Messages.ERR_INVALID_COLLECTOR_NAME_1, className));
348        } catch (ClassCastException e) {
349            throw new CmsConfigurationException(
350                Messages.get().container(Messages.ERR_INVALID_COLLECTOR_NAME_1, className));
351        }
352
353        // set the configured order for the collector
354        int ord = 0;
355        try {
356            ord = Integer.valueOf(order).intValue();
357        } catch (NumberFormatException e) {
358            LOG.error(Messages.get().getBundle().key(Messages.LOG_COLLECTOR_BAD_ORDER_NUMBER_1, className), e);
359        }
360        collector.setOrder(ord);
361
362        if (CmsLog.INIT.isInfoEnabled()) {
363            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_ADD_COLLECTOR_CLASS_2, className, order));
364        }
365
366        // extend or init the current list of configured collectors
367        if (m_collectors != null) {
368            m_collectors = new ArrayList<I_CmsResourceCollector>(m_collectors);
369            m_collectorNameMappings = new HashMap<String, I_CmsResourceCollector>(m_collectorNameMappings);
370        } else {
371            m_collectors = new ArrayList<I_CmsResourceCollector>();
372            m_collectorNameMappings = new HashMap<String, I_CmsResourceCollector>();
373        }
374
375        if (!m_collectors.contains(collector)) {
376            // this is a collector not currently configured
377            m_collectors.add(collector);
378
379            Iterator<String> i = collector.getCollectorNames().iterator();
380            while (i.hasNext()) {
381                String name = i.next();
382                if (m_collectorNameMappings.containsKey(name)) {
383                    // this name is already configured, check the order of the collector
384                    I_CmsResourceCollector otherCollector = m_collectorNameMappings.get(name);
385                    if (collector.getOrder() > otherCollector.getOrder()) {
386                        // new collector has a greater order than the old collector in the Map
387                        m_collectorNameMappings.put(name, collector);
388                        if (CmsLog.INIT.isInfoEnabled()) {
389                            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_COLLECTOR_REPLACED_1, name));
390                        }
391                    } else {
392                        if (CmsLog.INIT.isInfoEnabled()) {
393                            CmsLog.INIT.info(
394                                Messages.get().getBundle().key(Messages.INIT_DUPLICATE_COLLECTOR_SKIPPED_1, name));
395                        }
396                    }
397                } else {
398                    m_collectorNameMappings.put(name, collector);
399                    if (CmsLog.INIT.isInfoEnabled()) {
400                        CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_ADD_COLLECTOR_1, name));
401                    }
402                }
403            }
404        }
405
406        // ensure list is unmodifiable to avoid potential misuse or accidental changes
407        Collections.sort(m_collectors);
408        m_collectors = Collections.unmodifiableList(m_collectors);
409        m_collectorNameMappings = Collections.unmodifiableMap(m_collectorNameMappings);
410
411        // return the created collector instance
412        return collector;
413    }
414
415    /**
416     * Adds a new HTML converter class to internal list of loaded converter classes.<p>
417     *
418     * @param name the name of the option that should trigger the HTML converter class
419     * @param className the name of the class to add
420     *
421     * @return the created HTML converter instance
422     *
423     * @throws CmsConfigurationException in case the HTML converter could not be properly initialized
424     */
425    public I_CmsHtmlConverter addHtmlConverter(String name, String className) throws CmsConfigurationException {
426
427        // check if new conversion option can still be added
428        if (m_frozen) {
429            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
430        }
431
432        Class<?> classClazz;
433        // init class for content converter
434        try {
435            classClazz = Class.forName(className);
436        } catch (ClassNotFoundException e) {
437            LOG.error(Messages.get().getBundle().key(Messages.LOG_HTML_CONVERTER_CLASS_NOT_FOUND_1, className), e);
438            return null;
439        }
440
441        I_CmsHtmlConverter converter;
442        try {
443            converter = (I_CmsHtmlConverter)classClazz.newInstance();
444        } catch (InstantiationException e) {
445            throw new CmsConfigurationException(
446                Messages.get().container(Messages.ERR_INVALID_HTMLCONVERTER_NAME_1, className));
447        } catch (IllegalAccessException e) {
448            throw new CmsConfigurationException(
449                Messages.get().container(Messages.ERR_INVALID_HTMLCONVERTER_NAME_1, className));
450        } catch (ClassCastException e) {
451            throw new CmsConfigurationException(
452                Messages.get().container(Messages.ERR_INVALID_HTMLCONVERTER_NAME_1, className));
453        }
454
455        if (CmsLog.INIT.isInfoEnabled()) {
456            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_ADD_HTML_CONVERTER_CLASS_2, className, name));
457        }
458
459        m_configuredHtmlConverters.add(new CmsHtmlConverterOption(name, className));
460        return converter;
461    }
462
463    /**
464     * Adds a new loader to the internal list of loaded loaders.<p>
465     *
466     * @param loader the loader to add
467     * @throws CmsConfigurationException in case the resource manager configuration is already initialized
468     */
469    public void addLoader(I_CmsResourceLoader loader) throws CmsConfigurationException {
470
471        // check if new loaders can still be added
472        if (m_frozen) {
473            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
474        }
475
476        // add the loader to the internal list of loaders
477        int pos = loader.getLoaderId();
478        if (pos >= m_loaders.length) {
479            I_CmsResourceLoader[] buffer = new I_CmsResourceLoader[pos * 2];
480            System.arraycopy(m_loaders, 0, buffer, 0, m_loaders.length);
481            m_loaders = buffer;
482        }
483        m_loaders[pos] = loader;
484        m_loaderList.add(loader);
485        if (CmsLog.INIT.isInfoEnabled()) {
486            CmsLog.INIT.info(
487                Messages.get().getBundle().key(
488                    Messages.INIT_ADD_LOADER_2,
489                    loader.getClass().getName(),
490                    new Integer(pos)));
491        }
492    }
493
494    /**
495     * Adds a new MIME type from the XML configuration to the internal list of MIME types.<p>
496     *
497     * @param extension the MIME type extension
498     * @param type the MIME type description
499     *
500     * @return the created MIME type instance
501     *
502     * @throws CmsConfigurationException in case the resource manager configuration is already initialized
503     */
504    public CmsMimeType addMimeType(String extension, String type) throws CmsConfigurationException {
505
506        // check if new mime types can still be added
507        if (m_frozen) {
508            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
509        }
510
511        CmsMimeType mimeType = new CmsMimeType(extension, type);
512        m_configuredMimeTypes.add(mimeType);
513        return mimeType;
514    }
515
516    /**
517     * Adds a new relation type from the XML configuration to the list of user defined relation types.<p>
518     *
519     * @param name the name of the relation type
520     * @param type the type of the relation type, weak or strong
521     *
522     * @return the new created relation type instance
523     *
524     * @throws CmsConfigurationException in case the resource manager configuration is already initialized
525     */
526    public CmsRelationType addRelationType(String name, String type) throws CmsConfigurationException {
527
528        // check if new relation types can still be added
529        if (m_frozen) {
530            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
531        }
532
533        CmsRelationType relationType = new CmsRelationType(m_configuredRelationTypes.size(), name, type);
534        m_configuredRelationTypes.add(relationType);
535        return relationType;
536    }
537
538    /**
539     * Adds a new resource type from the XML configuration to the internal list of loaded resource types.<p>
540     *
541     * Resource types can also be added from a module.<p>
542     *
543     * @param resourceType the resource type to add
544     * @throws CmsConfigurationException in case the resource manager configuration is already initialized
545     */
546    public void addResourceType(I_CmsResourceType resourceType) throws CmsConfigurationException {
547
548        // check if new resource types can still be added
549        if (m_frozen) {
550            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
551        }
552
553        I_CmsResourceType conflictingType = null;
554        if (resourceType.getTypeId() == CmsResourceTypeUnknownFile.RESOURCE_TYPE_ID) {
555            // default unknown file resource type
556            if (m_restypeUnknownFile != null) {
557                // error: already set
558                conflictingType = m_restypeUnknownFile;
559            } else {
560                m_restypeUnknownFile = resourceType;
561                return;
562            }
563        } else if (resourceType.getTypeId() == CmsResourceTypeUnknownFolder.RESOURCE_TYPE_ID) {
564            // default unknown folder resource type
565            if (m_restypeUnknownFolder != null) {
566                // error: already set
567                conflictingType = m_restypeUnknownFolder;
568            } else {
569                m_restypeUnknownFolder = resourceType;
570                return;
571            }
572        } else {
573            // normal resource types
574            int conflictIndex = m_resourceTypesFromXml.indexOf(resourceType);
575            if (conflictIndex >= 0) {
576                conflictingType = m_resourceTypesFromXml.get(conflictIndex);
577            }
578        }
579        if (conflictingType != null) {
580            // configuration problem: the resource type (or at least the id or the name) is already configured
581            throw new CmsConfigurationException(
582                Messages.get().container(
583                    Messages.ERR_CONFLICTING_RESOURCE_TYPES_4,
584                    new Object[] {
585                        resourceType.getTypeName(),
586                        new Integer(resourceType.getTypeId()),
587                        conflictingType.getTypeName(),
588                        new Integer(conflictingType.getTypeId())}));
589        }
590
591        m_resourceTypesFromXml.add(resourceType);
592    }
593
594    /**
595     * Gets the map of forbidden contexts for resource types.<p>
596     *
597     * @param cms the current CMS context
598     * @return the map from resource types to the forbidden contexts
599     */
600    public Map<String, CmsDefaultSet<String>> getAllowedContextMap(CmsObject cms) {
601
602        Map<String, CmsDefaultSet<String>> result = new HashMap<String, CmsDefaultSet<String>>();
603        for (I_CmsResourceType resType : getResourceTypes()) {
604            if (resType instanceof CmsResourceTypeXmlContent) {
605                String schema = null;
606                try {
607                    schema = ((CmsResourceTypeXmlContent)resType).getSchema();
608                    if (schema != null) {
609                        CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, schema);
610
611                        CmsDefaultSet<String> allowedContexts = contentDefinition.getContentHandler().getAllowedTemplates();
612                        result.put(resType.getTypeName(), allowedContexts);
613                    } else {
614                        LOG.info(
615                            "No schema for XML type " + resType.getTypeName() + " / " + resType.getClass().getName());
616                    }
617                } catch (Exception e) {
618                    LOG.error(
619                        "Error in getAllowedContextMap, schema="
620                            + schema
621                            + ", type="
622                            + resType.getTypeName()
623                            + ", "
624                            + e.getLocalizedMessage(),
625                        e);
626                }
627            }
628        }
629        return result;
630    }
631
632    /**
633     * Returns the configured content collector with the given name, or <code>null</code> if
634     * no collector with this name is configured.<p>
635     *
636     * @param collectorName the name of the collector to get
637     * @return the configured content collector with the given name
638     */
639    public I_CmsResourceCollector getContentCollector(String collectorName) {
640
641        return m_collectorNameMappings.get(collectorName);
642    }
643
644    /**
645     * Returns the default resource type for the given resource name, using the
646     * configured resource type file extensions.<p>
647     *
648     * In case the given name does not map to a configured resource type,
649     * {@link CmsResourceTypePlain} is returned.<p>
650     *
651     * This is only required (and should <i>not</i> be used otherwise) when
652     * creating a new resource automatically during file upload or synchronization.
653     * Only in this case, the file type for the new resource is determined using this method.
654     * Otherwise the resource type is <i>always</i> stored as part of the resource,
655     * and is <i>not</i> related to the file name.<p>
656     *
657     * @param resourcename the resource name to look up the resource type for
658     *
659     * @return the default resource type for the given resource name
660     *
661     * @throws CmsException if something goes wrong
662     */
663    public I_CmsResourceType getDefaultTypeForName(String resourcename) throws CmsException {
664
665        String typeName = null;
666        String suffix = null;
667        if (CmsStringUtil.isNotEmpty(resourcename)) {
668            int pos = resourcename.lastIndexOf('.');
669            if (pos >= 0) {
670                suffix = resourcename.substring(pos);
671                if (CmsStringUtil.isNotEmpty(suffix)) {
672                    suffix = suffix.toLowerCase();
673                    typeName = m_configuration.m_extensionMappings.get(suffix);
674
675                }
676            }
677        }
678
679        if (typeName == null) {
680            // use default type "plain"
681            typeName = CmsResourceTypePlain.getStaticTypeName();
682        }
683
684        if (CmsLog.INIT.isDebugEnabled()) {
685            CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_GET_RESTYPE_2, typeName, suffix));
686        }
687        // look up and return the resource type
688        return getResourceType(typeName);
689    }
690
691    /**
692     * Returns the file extensions (suffixes) mappings to resource types.<p>
693     *
694     * @return a Map with all known file extensions as keys and their resource types as values.
695     */
696    public Map<String, String> getExtensionMapping() {
697
698        return m_configuration.m_extensionMappings;
699    }
700
701    /**
702     * Returns the file translator.<p>
703     *
704     * @return the file translator
705     */
706    public CmsResourceTranslator getFileTranslator() {
707
708        return m_fileTranslator;
709    }
710
711    /**
712     * Returns the folder translator.<p>
713     *
714     * @return the folder translator
715     */
716    public CmsResourceTranslator getFolderTranslator() {
717
718        return m_folderTranslator;
719    }
720
721    /**
722     * Returns the matching HTML converter class name for the specified option name.<p>
723     *
724     * @param name the name of the option that should trigger the HTML converter class
725     *
726     * @return the matching HTML converter class name for the specified option name or <code>null</code> if no match is found
727     */
728    public String getHtmlConverter(String name) {
729
730        return m_htmlConverters.get(name);
731    }
732
733    /**
734     * Returns an unmodifiable List of the configured {@link CmsHtmlConverterOption} objects.<p>
735     *
736     * @return an unmodifiable List of the configured {@link CmsHtmlConverterOption} objects
737     */
738    public List<CmsHtmlConverterOption> getHtmlConverters() {
739
740        return m_configuredHtmlConverters;
741    }
742
743    /**
744     * Returns the loader class instance for a given resource.<p>
745     *
746     * @param resource the resource
747     * @return the appropriate loader class instance
748     * @throws CmsLoaderException if something goes wrong
749     */
750    public I_CmsResourceLoader getLoader(CmsResource resource) throws CmsLoaderException {
751
752        return getLoader(getResourceType(resource.getTypeId()).getLoaderId());
753    }
754
755    /**
756     * Returns the loader class instance for the given loader id.<p>
757     *
758     * @param id the id of the loader to return
759     * @return the loader class instance for the given loader id
760     */
761    public I_CmsResourceLoader getLoader(int id) {
762
763        return m_loaders[id];
764    }
765
766    /**
767     * Returns the (unmodifiable array) list with all initialized resource loaders.<p>
768     *
769     * @return the (unmodifiable array) list with all initialized resource loaders
770     */
771    public List<I_CmsResourceLoader> getLoaders() {
772
773        return m_loaderList;
774    }
775
776    /**
777     * Returns the MIME type for a specified file name.<p>
778     *
779     * If an encoding parameter that is not <code>null</code> is provided,
780     * the returned MIME type is extended with a <code>; charset={encoding}</code> setting.<p>
781     *
782     * If no MIME type for the given filename can be determined, the
783     * default <code>{@link #MIMETYPE_HTML}</code> is used.<p>
784     *
785     * @param filename the file name to check the MIME type for
786     * @param encoding the default encoding (charset) in case of MIME types is of type "text"
787     *
788     * @return the MIME type for a specified file
789     */
790    public String getMimeType(String filename, String encoding) {
791
792        return getMimeType(filename, encoding, MIMETYPE_HTML);
793    }
794
795    /**
796     * Returns the MIME type for a specified file name.<p>
797     *
798     * If an encoding parameter that is not <code>null</code> is provided,
799     * the returned MIME type is extended with a <code>; charset={encoding}</code> setting.<p>
800     *
801     * If no MIME type for the given filename can be determined, the
802     * provided default is used.<p>
803     *
804     * @param filename the file name to check the MIME type for
805     * @param encoding the default encoding (charset) in case of MIME types is of type "text"
806     * @param defaultMimeType the default MIME type to use if no matching type for the filename is found
807     *
808     * @return the MIME type for a specified file
809     */
810    public String getMimeType(String filename, String encoding, String defaultMimeType) {
811
812        String mimeType = null;
813        int lastDot = filename.lastIndexOf('.');
814        // check the MIME type for the file extension
815        if ((lastDot > 0) && (lastDot < (filename.length() - 1))) {
816            mimeType = m_mimeTypes.get(filename.substring(lastDot).toLowerCase(Locale.ENGLISH));
817        }
818        if (mimeType == null) {
819            mimeType = defaultMimeType;
820            if (mimeType == null) {
821                // no default MIME type was provided
822                return null;
823            }
824        }
825        StringBuffer result = new StringBuffer(mimeType);
826        if ((encoding != null) && mimeType.startsWith("text") && (mimeType.indexOf("charset") == -1)) {
827            result.append("; charset=");
828            result.append(encoding);
829        }
830        return result.toString();
831    }
832
833    /**
834     * Returns an unmodifiable List of the configured {@link CmsMimeType} objects.<p>
835     *
836     * @return an unmodifiable List of the configured {@link CmsMimeType} objects
837     */
838    public List<CmsMimeType> getMimeTypes() {
839
840        return m_configuredMimeTypes;
841    }
842
843    /**
844     * Returns the name generator for XML content file names.<p>
845     *
846     * @return the name generator for XML content file names.
847     */
848    public I_CmsFileNameGenerator getNameGenerator() {
849
850        if (m_nameGenerator == null) {
851            m_nameGenerator = new CmsDefaultFileNameGenerator();
852        }
853        return m_nameGenerator;
854    }
855
856    /**
857     * Returns an (unmodifiable) list of class names of all currently registered content collectors
858     * ({@link I_CmsResourceCollector} objects).<p>
859     *
860     * @return an (unmodifiable) list of class names of all currently registered content collectors
861     *      ({@link I_CmsResourceCollector} objects)
862     */
863    public List<I_CmsResourceCollector> getRegisteredContentCollectors() {
864
865        return m_collectors;
866    }
867
868    /**
869     * Returns an unmodifiable List of the configured {@link CmsRelationType} objects.<p>
870     *
871     * @return an unmodifiable List of the configured {@link CmsRelationType} objects
872     */
873    public List<CmsRelationType> getRelationTypes() {
874
875        return m_configuredRelationTypes;
876    }
877
878    /**
879     * Convenience method to get the initialized resource type instance for the given resource,
880     * with a fall back to special "unknown" resource types in case the resource type is not configured.<p>
881     *
882     * @param resource the resource to get the type for
883     *
884     * @return the initialized resource type instance for the given resource
885     */
886    public I_CmsResourceType getResourceType(CmsResource resource) {
887
888        I_CmsResourceType result = m_configuration.getResourceTypeById(resource.getTypeId());
889        if (result == null) {
890            // this resource type is unknown, return the default files instead
891            if (resource.isFolder()) {
892                // resource is a folder
893                if (m_restypeUnknownFolder != null) {
894                    result = m_restypeUnknownFolder;
895                } else {
896                    result = m_configuration.getResourceTypeByName(CmsResourceTypeFolder.getStaticTypeName());
897                }
898            } else {
899                // resource is a file
900                if (m_restypeUnknownFile != null) {
901                    result = m_restypeUnknownFile;
902                } else {
903                    result = m_configuration.getResourceTypeByName(CmsResourceTypeBinary.getStaticTypeName());
904                }
905            }
906        }
907        return result;
908    }
909
910    /**
911     * Returns the initialized resource type instance for the given id.<p>
912     *
913     * @param typeId the id of the resource type to get
914     *
915     * @return the initialized resource type instance for the given id
916     *
917     * @throws CmsLoaderException if no resource type is available for the given id
918     */
919    public I_CmsResourceType getResourceType(int typeId) throws CmsLoaderException {
920
921        I_CmsResourceType result = m_configuration.getResourceTypeById(typeId);
922        if (result == null) {
923            throw new CmsLoaderException(
924                Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_ID_REQ_1, new Integer(typeId)));
925        }
926        return result;
927    }
928
929    /**
930     * Returns the initialized resource type instance for the given resource type name.<p>
931     *
932     * @param typeName the name of the resource type to get
933     *
934     * @return the initialized resource type instance for the given name
935     *
936     * @throws CmsLoaderException if no resource type is available for the given name
937     */
938    public I_CmsResourceType getResourceType(String typeName) throws CmsLoaderException {
939
940        I_CmsResourceType result = m_configuration.getResourceTypeByName(typeName);
941        if (result != null) {
942            return result;
943        }
944        throw new CmsLoaderException(Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_NAME_REQ_1, typeName));
945    }
946
947    /**
948     * Returns the (unmodifiable) list with all initialized resource types.<p>
949     *
950     * @return the (unmodifiable) list with all initialized resource types
951     */
952    public List<I_CmsResourceType> getResourceTypes() {
953
954        return m_configuration.m_resourceTypeList;
955    }
956
957    /**
958     * Returns the (unmodifiable) list with all initialized resource types including unknown types.<p>
959     *
960     * @return the (unmodifiable) list with all initialized resource types including unknown types
961     */
962    public List<I_CmsResourceType> getResourceTypesWithUnknown() {
963
964        return m_configuration.m_resourceTypeListWithUnknown;
965    }
966
967    /**
968     * The configured default type for files when the resource type is missing.<p>
969     *
970     * @return the configured default type for files
971     */
972    public I_CmsResourceType getResTypeUnknownFile() {
973
974        return m_restypeUnknownFile;
975    }
976
977    /**
978     * The configured default type for folders when the resource type is missing.<p>
979     *
980     * @return The configured default type for folders
981     */
982    public I_CmsResourceType getResTypeUnknownFolder() {
983
984        return m_restypeUnknownFolder;
985    }
986
987    /**
988     * Returns a template loader facade for the given file.<p>
989     * @param cms the current OpenCms user context
990     * @param resource the requested file
991     * @param templateProperty the property to read for the template
992     *
993     * @return a resource loader facade for the given file
994     * @throws CmsException if something goes wrong
995     */
996    public CmsTemplateLoaderFacade getTemplateLoaderFacade(CmsObject cms, CmsResource resource, String templateProperty)
997    throws CmsException {
998
999        return getTemplateLoaderFacade(cms, null, resource, templateProperty);
1000    }
1001
1002    /**
1003     * Returns a template loader facade for the given file.<p>
1004     * @param cms the current OpenCms user context
1005     * @param request the current request
1006     * @param resource the requested file
1007     * @param templateProperty the property to read for the template
1008     *
1009     * @return a resource loader facade for the given file
1010     * @throws CmsException if something goes wrong
1011     */
1012    public CmsTemplateLoaderFacade getTemplateLoaderFacade(
1013        CmsObject cms,
1014        HttpServletRequest request,
1015        CmsResource resource,
1016        String templateProperty) throws CmsException {
1017
1018        String templateProp = cms.readPropertyObject(resource, templateProperty, true).getValue();
1019        CmsTemplateContext templateContext = null;
1020        String templateName = null;
1021        if (templateProp == null) {
1022
1023            // use default template, if template is not set
1024            templateProp = DEFAULT_TEMPLATE;
1025            NamedTemplate namedTemplate = readTemplateWithName(cms, templateProp);
1026            if (namedTemplate == null) {
1027                // no template property defined, this is a must for facade loaders
1028                throw new CmsLoaderException(
1029                    Messages.get().container(Messages.ERR_NONDEF_PROP_2, templateProperty, cms.getSitePath(resource)));
1030            }
1031            templateName = namedTemplate.getName();
1032        } else {
1033            if ((request != null) && CmsTemplateContextManager.hasPropertyPrefix(templateProp)) {
1034                templateContext = OpenCms.getTemplateContextManager().getTemplateContext(
1035                    templateProp,
1036                    cms,
1037                    request,
1038                    resource);
1039                if (templateContext != null) {
1040                    templateProp = templateContext.getTemplatePath();
1041                }
1042            }
1043            NamedTemplate namedTemplate = readTemplateWithName(cms, templateProp);
1044            if (namedTemplate == null) {
1045                namedTemplate = readTemplateWithName(cms, DEFAULT_TEMPLATE);
1046                if (namedTemplate != null) {
1047                    templateProp = DEFAULT_TEMPLATE;
1048                    templateName = namedTemplate.getName();
1049                }
1050            } else {
1051                templateName = namedTemplate.getName();
1052            }
1053        }
1054        CmsResource template = cms.readFile(templateProp, CmsResourceFilter.IGNORE_EXPIRATION);
1055        CmsTemplateLoaderFacade result = new CmsTemplateLoaderFacade(getLoader(template), resource, template);
1056        result.setTemplateContext(templateContext);
1057        result.setTemplateName(templateName);
1058        return result;
1059
1060    }
1061
1062    /**
1063     * Returns the XSD translator.<p>
1064     *
1065     * @return the XSD translator
1066     */
1067    public CmsResourceTranslator getXsdTranslator() {
1068
1069        return m_xsdTranslator;
1070    }
1071
1072    /**
1073     * Checks if an initialized resource type instance equal to the given resource type is available.<p>
1074     *
1075     * @param type the resource type to check
1076     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1077     *
1078     * @see #getResourceType(String)
1079     * @see #getResourceType(int)
1080     */
1081    public boolean hasResourceType(I_CmsResourceType type) {
1082
1083        return hasResourceType(type.getTypeName());
1084    }
1085
1086    /**
1087     * Checks if an initialized resource type instance for the given resource type is is available.<p>
1088     *
1089     * @param typeId the id of the resource type to check
1090     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1091     *
1092     * @see #getResourceType(int)
1093     *
1094     * @deprecated
1095     * Use {@link #hasResourceType(I_CmsResourceType)} or {@link #hasResourceType(I_CmsResourceType)} instead.
1096     * Resource types should always be referenced either by its type class (preferred) or by type name.
1097     * Use of int based resource type references will be discontinued in a future OpenCms release.
1098     */
1099    @Deprecated
1100    public boolean hasResourceType(int typeId) {
1101
1102        return m_configuration.getResourceTypeById(typeId) != null;
1103    }
1104
1105    /**
1106     * Checks if an initialized resource type instance for the given resource type name is available.<p>
1107     *
1108     * @param typeName the name of the resource type to check
1109     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1110     *
1111     * @see #getResourceType(String)
1112     */
1113    public boolean hasResourceType(String typeName) {
1114
1115        return m_configuration.getResourceTypeByName(typeName) != null;
1116    }
1117
1118    /**
1119     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration()
1120     *
1121     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1122     */
1123    public void initConfiguration() throws CmsConfigurationException {
1124
1125        if (CmsLog.INIT.isInfoEnabled()) {
1126            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_LOADER_CONFIG_FINISHED_0));
1127        }
1128
1129        m_resourceTypesFromXml = Collections.unmodifiableList(m_resourceTypesFromXml);
1130        m_loaderList = Collections.unmodifiableList(m_loaderList);
1131        Collections.sort(m_configuredMimeTypes);
1132        m_configuredMimeTypes = Collections.unmodifiableList(m_configuredMimeTypes);
1133        m_configuredRelationTypes = Collections.unmodifiableList(m_configuredRelationTypes);
1134
1135        // initialize the HTML converters
1136        initHtmlConverters();
1137        m_configuredHtmlConverters = Collections.unmodifiableList(m_configuredHtmlConverters);
1138
1139        // initialize the resource types
1140        initResourceTypes();
1141        // initialize the MIME types
1142        initMimeTypes();
1143    }
1144
1145    /**
1146     * Initializes all additional resource types stored in the modules.<p>
1147     *
1148     * @param cms an initialized OpenCms user context with "module manager" role permissions
1149     *
1150     * @throws CmsRoleViolationException in case the provided OpenCms user context did not have "module manager" role permissions
1151     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1152     */
1153    public synchronized void initialize(CmsObject cms) throws CmsRoleViolationException, CmsConfigurationException {
1154
1155        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
1156            // some simple test cases don't require this check
1157            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
1158        }
1159
1160        // initialize the resource types
1161        initResourceTypes();
1162
1163        // call initialize method on all resource types
1164        Iterator<I_CmsResourceType> i = m_configuration.m_resourceTypeList.iterator();
1165        while (i.hasNext()) {
1166            I_CmsResourceType type = i.next();
1167            type.initialize(cms);
1168        }
1169
1170        if (CmsLog.INIT.isInfoEnabled()) {
1171            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_LOADER_CONFIG_FINISHED_0));
1172        }
1173    }
1174
1175    /**
1176     * Loads the requested resource and writes the contents to the response stream.<p>
1177     *
1178     * @param req the current HTTP request
1179     * @param res the current HTTP response
1180     * @param cms the current OpenCms user context
1181     * @param resource the requested resource
1182     * @throws ServletException if something goes wrong
1183     * @throws IOException if something goes wrong
1184     * @throws CmsException if something goes wrong
1185     */
1186    public void loadResource(CmsObject cms, CmsResource resource, HttpServletRequest req, HttpServletResponse res)
1187    throws ServletException, IOException, CmsException {
1188
1189        res.setContentType(getMimeType(resource.getName(), cms.getRequestContext().getEncoding()));
1190        I_CmsResourceLoader loader = getLoader(resource);
1191        loader.load(cms, resource, req, res);
1192    }
1193
1194    /**
1195     * Checks if there is a resource type with a given name whose id matches the given id.<p>
1196     *
1197     * This will return 'false' if no resource type with the given name is registered.<p>
1198     *
1199     * @param name a resource type name
1200     * @param id a resource type id
1201     *
1202     * @return true if a matching resource type with the given name and id was found
1203     */
1204    public boolean matchResourceType(String name, int id) {
1205
1206        if (hasResourceType(name)) {
1207            try {
1208                return getResourceType(name).getTypeId() == id;
1209            } catch (Exception e) {
1210                // should never happen because we already checked with hasResourceType, still have to
1211                // catch it so the compiler is happy
1212                LOG.error(e.getLocalizedMessage(), e);
1213                return false;
1214            }
1215        } else {
1216            return false;
1217        }
1218    }
1219
1220    /**
1221     * Configures the URL name generator for XML contents.<p>
1222     *
1223     * @param nameGenerator the configured name generator class
1224     *
1225     * @throws CmsConfigurationException if something goes wrong
1226     */
1227    public void setNameGenerator(I_CmsFileNameGenerator nameGenerator) throws CmsConfigurationException {
1228
1229        if (m_frozen) {
1230            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
1231        }
1232        m_nameGenerator = nameGenerator;
1233    }
1234
1235    /**
1236     * Sets the folder, the file and the XSD translator.<p>
1237     *
1238     * @param folderTranslator the folder translator to set
1239     * @param fileTranslator the file translator to set
1240     * @param xsdTranslator the XSD translator to set
1241     */
1242    public void setTranslators(
1243        CmsResourceTranslator folderTranslator,
1244        CmsResourceTranslator fileTranslator,
1245        CmsResourceTranslator xsdTranslator) {
1246
1247        m_folderTranslator = folderTranslator;
1248        m_fileTranslator = fileTranslator;
1249        m_xsdTranslator = xsdTranslator;
1250    }
1251
1252    /**
1253     * Shuts down this resource manage instance.<p>
1254     *
1255     * @throws Exception in case of errors during shutdown
1256     */
1257    public synchronized void shutDown() throws Exception {
1258
1259        Iterator<I_CmsResourceLoader> it = m_loaderList.iterator();
1260        while (it.hasNext()) {
1261            // destroy all resource loaders
1262            I_CmsResourceLoader loader = it.next();
1263            loader.destroy();
1264        }
1265
1266        m_loaderList = null;
1267        m_loaders = null;
1268        m_collectorNameMappings = null;
1269        m_mimeTypes = null;
1270        m_configuredMimeTypes = null;
1271        m_configuredRelationTypes = null;
1272        m_configuredHtmlConverters = null;
1273        m_htmlConverters = null;
1274
1275        if (CmsLog.INIT.isInfoEnabled()) {
1276            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName()));
1277        }
1278    }
1279
1280    /**
1281     * Gets the template name for a template resource, using a cache for efficiency.<p>
1282     *
1283     * @param cms the current CMS context
1284     * @param resource the template resource
1285     * @return the template name
1286     *
1287     * @throws CmsException if something goes wrong
1288     */
1289    private String getTemplateName(CmsObject cms, CmsResource resource) throws CmsException {
1290
1291        String templateName = (String)(m_templateNameCache.getCachedObject(cms, resource.getRootPath()));
1292        if (templateName == null) {
1293            CmsProperty nameProperty = cms.readPropertyObject(
1294                resource,
1295                CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS,
1296                false);
1297            String nameFromProperty = "";
1298            if (!nameProperty.isNullProperty()) {
1299                nameFromProperty = nameProperty.getValue();
1300            }
1301            m_templateNameCache.putCachedObject(cms, resource.getRootPath(), nameFromProperty);
1302            return nameFromProperty;
1303        } else {
1304            return templateName;
1305        }
1306    }
1307
1308    /**
1309     * Initialize the HTML converters.<p>
1310     *
1311     * HTML converters are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1312     *
1313     * For legacy reasons, the default JTidy HTML converter has to be loaded if no explicit HTML converters
1314     * are configured in the configuration file.<p>
1315     */
1316    private void initHtmlConverters() {
1317
1318        // check if any HTML converter configuration were found
1319        if (m_configuredHtmlConverters.size() == 0) {
1320            // no converters configured, add default JTidy converter configuration
1321            String classJTidy = CmsHtmlConverterJTidy.class.getName();
1322            m_configuredHtmlConverters.add(
1323                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_ENABLED, classJTidy, true));
1324            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_XHTML, classJTidy, true));
1325            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_WORD, classJTidy, true));
1326            m_configuredHtmlConverters.add(
1327                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_REPLACE_PARAGRAPHS, classJTidy, true));
1328        }
1329
1330        // initialize lookup map of configured HTML converters
1331        m_htmlConverters = new HashMap<String, String>(m_configuredHtmlConverters.size());
1332        for (Iterator<CmsHtmlConverterOption> i = m_configuredHtmlConverters.iterator(); i.hasNext();) {
1333            CmsHtmlConverterOption converterOption = i.next();
1334            m_htmlConverters.put(converterOption.getName(), converterOption.getClassName());
1335        }
1336    }
1337
1338    /**
1339     * Initialize the MIME types.<p>
1340     *
1341     * MIME types are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1342     *
1343     * For legacy reasons, the MIME types are also read from a file <code>"mimetypes.properties"</code>
1344     * that must be located in the default <code>"classes"</code> folder of the web application.<p>
1345     */
1346    private void initMimeTypes() {
1347
1348        // legacy MIME type initialization: try to read properties file
1349        Properties mimeTypes = new Properties();
1350        try {
1351            // first try: read MIME types from default package
1352            mimeTypes.load(getClass().getClassLoader().getResourceAsStream("mimetypes.properties"));
1353        } catch (Throwable t) {
1354            try {
1355                // second try: read MIME types from loader package (legacy reasons, there are no types by default)
1356                mimeTypes.load(
1357                    getClass().getClassLoader().getResourceAsStream("org/opencms/loader/mimetypes.properties"));
1358            } catch (Throwable t2) {
1359                if (LOG.isInfoEnabled()) {
1360                    LOG.info(
1361                        Messages.get().getBundle().key(
1362                            Messages.LOG_READ_MIMETYPES_FAILED_2,
1363                            "mimetypes.properties",
1364                            "org/opencms/loader/mimetypes.properties"));
1365                }
1366            }
1367        }
1368
1369        // initialize the Map with all available MIME types
1370        List<CmsMimeType> combinedMimeTypes = new ArrayList<CmsMimeType>(
1371            mimeTypes.size() + m_configuredMimeTypes.size());
1372        // first add all MIME types from the configuration
1373        combinedMimeTypes.addAll(m_configuredMimeTypes);
1374        // now add the MIME types from the properties
1375        Iterator<Map.Entry<Object, Object>> i = mimeTypes.entrySet().iterator();
1376        while (i.hasNext()) {
1377            Map.Entry<Object, Object> entry = i.next();
1378            CmsMimeType mimeType = new CmsMimeType(entry.getKey().toString(), entry.getValue().toString(), false);
1379            if (!combinedMimeTypes.contains(mimeType)) {
1380                // make sure no MIME types from the XML configuration are overwritten
1381                combinedMimeTypes.add(mimeType);
1382            }
1383        }
1384
1385        // create a lookup Map for the MIME types
1386        m_mimeTypes = new HashMap<String, String>(mimeTypes.size());
1387        Iterator<CmsMimeType> j = combinedMimeTypes.iterator();
1388        while (j.hasNext()) {
1389            CmsMimeType mimeType = j.next();
1390            m_mimeTypes.put(mimeType.getExtension(), mimeType.getType());
1391        }
1392
1393        if (CmsLog.INIT.isInfoEnabled()) {
1394            CmsLog.INIT.info(
1395                Messages.get().getBundle().key(Messages.INIT_NUM_MIMETYPES_1, new Integer(m_mimeTypes.size())));
1396        }
1397    }
1398
1399    /**
1400     * Adds a new resource type to the internal list of loaded resource types and initializes
1401     * options for the resource type.<p>
1402     *
1403     * @param resourceType the resource type to add
1404     * @param configuration the resource configuration
1405     */
1406    private synchronized void initResourceType(
1407        I_CmsResourceType resourceType,
1408        CmsResourceManagerConfiguration configuration) {
1409
1410        // add the loader to the internal list of loaders
1411        configuration.addResourceType(resourceType);
1412        if (CmsLog.INIT.isInfoEnabled()) {
1413            CmsLog.INIT.info(
1414                Messages.get().getBundle().key(
1415                    Messages.INIT_ADD_RESTYPE_3,
1416                    resourceType.getTypeName(),
1417                    new Integer(resourceType.getTypeId()),
1418                    resourceType.getClass().getName()));
1419        }
1420
1421        // add the mappings
1422        List<String> mappings = resourceType.getConfiguredMappings();
1423        Iterator<String> i = mappings.iterator();
1424        while (i.hasNext()) {
1425            String mapping = i.next();
1426            // only add this mapping if a mapping with this file extension does not
1427            // exist already
1428            if (!configuration.m_extensionMappings.containsKey(mapping)) {
1429                configuration.m_extensionMappings.put(mapping, resourceType.getTypeName());
1430                if (CmsLog.INIT.isInfoEnabled()) {
1431                    CmsLog.INIT.info(
1432                        Messages.get().getBundle().key(
1433                            Messages.INIT_MAP_RESTYPE_2,
1434                            mapping,
1435                            resourceType.getTypeName()));
1436                }
1437            }
1438        }
1439    }
1440
1441    /**
1442     * Initializes member variables required for storing the resource types.<p>
1443     *
1444     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1445     */
1446    private synchronized void initResourceTypes() throws CmsConfigurationException {
1447
1448        if (CmsLog.INIT.isInfoEnabled()) {
1449            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STARTING_LOADER_CONFIG_0));
1450        }
1451
1452        CmsResourceManagerConfiguration newConfiguration = new CmsResourceManagerConfiguration();
1453
1454        if (CmsLog.INIT.isInfoEnabled()) {
1455            CmsLog.INIT.info(
1456                Messages.get().getBundle().key(
1457                    Messages.INIT_ADD_RESTYPE_FROM_FILE_2,
1458                    new Integer(m_resourceTypesFromXml.size()),
1459                    CmsVfsConfiguration.DEFAULT_XML_FILE_NAME));
1460        }
1461
1462        // build a new resource type list from the resource types of the XML configuration
1463        Iterator<I_CmsResourceType> i;
1464        i = m_resourceTypesFromXml.iterator();
1465        while (i.hasNext()) {
1466            I_CmsResourceType resourceType = i.next();
1467            initResourceType(resourceType, newConfiguration);
1468        }
1469
1470        // add all resource types declared in the modules
1471        CmsModuleManager moduleManager = OpenCms.getModuleManager();
1472        if (moduleManager != null) {
1473            Iterator<String> modules = moduleManager.getModuleNames().iterator();
1474            while (modules.hasNext()) {
1475                CmsModule module = moduleManager.getModule(modules.next());
1476                if ((module != null) && (module.getResourceTypes().size() > 0)) {
1477                    // module contains resource types
1478                    if (CmsLog.INIT.isInfoEnabled()) {
1479                        CmsLog.INIT.info(
1480                            Messages.get().getBundle().key(
1481                                Messages.INIT_ADD_NUM_RESTYPES_FROM_MOD_2,
1482                                new Integer(module.getResourceTypes().size()),
1483                                module.getName()));
1484                    }
1485
1486                    Iterator<I_CmsResourceType> j = module.getResourceTypes().iterator();
1487                    while (j.hasNext()) {
1488                        I_CmsResourceType resourceType = j.next();
1489                        I_CmsResourceType conflictingType = null;
1490                        if (resourceType.getTypeId() == CmsResourceTypeUnknownFile.RESOURCE_TYPE_ID) {
1491                            // default unknown file resource type
1492                            if (m_restypeUnknownFile != null) {
1493                                // error: already set
1494                                conflictingType = m_restypeUnknownFile;
1495                            } else {
1496                                m_restypeUnknownFile = resourceType;
1497                                continue;
1498                            }
1499                        } else if (resourceType.getTypeId() == CmsResourceTypeUnknownFolder.RESOURCE_TYPE_ID) {
1500                            // default unknown folder resource type
1501                            if (m_restypeUnknownFolder != null) {
1502                                // error: already set
1503                                conflictingType = m_restypeUnknownFolder;
1504                            } else {
1505                                m_restypeUnknownFile = resourceType;
1506                                continue;
1507                            }
1508                        } else {
1509                            // normal resource types
1510                            conflictingType = newConfiguration.getResourceTypeById(resourceType.getTypeId());
1511                        }
1512                        if (conflictingType != null) {
1513                            throw new CmsConfigurationException(
1514                                Messages.get().container(
1515                                    Messages.ERR_CONFLICTING_MODULE_RESOURCE_TYPES_5,
1516                                    new Object[] {
1517                                        resourceType.getTypeName(),
1518                                        new Integer(resourceType.getTypeId()),
1519                                        module.getName(),
1520                                        conflictingType.getTypeName(),
1521                                        new Integer(conflictingType.getTypeId())}));
1522                        }
1523                        initResourceType(resourceType, newConfiguration);
1524                    }
1525                }
1526            }
1527        }
1528
1529        // freeze the current configuration
1530        newConfiguration.freeze(m_restypeUnknownFile, m_restypeUnknownFile);
1531        m_configuration = newConfiguration;
1532        m_frozen = true;
1533
1534        if (CmsLog.INIT.isInfoEnabled()) {
1535            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_RESOURCE_TYPE_INITIALIZED_0));
1536        }
1537    }
1538
1539    /**
1540     * Reads a template resource together with its name.<p>
1541     *
1542     * @param cms the current CMS context
1543     * @param path the template path
1544     *
1545     * @return the template together with its name, or null if the template couldn't be read
1546     */
1547    private NamedTemplate readTemplateWithName(CmsObject cms, String path) {
1548
1549        try {
1550            CmsResource resource = cms.readResource(path, CmsResourceFilter.IGNORE_EXPIRATION);
1551            String name = getTemplateName(cms, resource);
1552            return new NamedTemplate(resource, name);
1553        } catch (Exception e) {
1554            return null;
1555        }
1556    }
1557
1558}