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.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 = new CmsDefaultFileNameGenerator();
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)
827            && (mimeType.startsWith("text") || mimeType.endsWith("javascript"))
828            && (mimeType.indexOf("charset") == -1)) {
829            result.append("; charset=");
830            result.append(encoding);
831        }
832        return result.toString();
833    }
834
835    /**
836     * Returns an unmodifiable List of the configured {@link CmsMimeType} objects.<p>
837     *
838     * @return an unmodifiable List of the configured {@link CmsMimeType} objects
839     */
840    public List<CmsMimeType> getMimeTypes() {
841
842        return m_configuredMimeTypes;
843    }
844
845    /**
846     * Returns the name generator for XML content file names.<p>
847     *
848     * @return the name generator for XML content file names.
849     */
850    public I_CmsFileNameGenerator getNameGenerator() {
851
852        return m_nameGenerator;
853    }
854
855    /**
856     * Returns an (unmodifiable) list of class names of all currently registered content collectors
857     * ({@link I_CmsResourceCollector} objects).<p>
858     *
859     * @return an (unmodifiable) list of class names of all currently registered content collectors
860     *      ({@link I_CmsResourceCollector} objects)
861     */
862    public List<I_CmsResourceCollector> getRegisteredContentCollectors() {
863
864        return m_collectors;
865    }
866
867    /**
868     * Returns an unmodifiable List of the configured {@link CmsRelationType} objects.<p>
869     *
870     * @return an unmodifiable List of the configured {@link CmsRelationType} objects
871     */
872    public List<CmsRelationType> getRelationTypes() {
873
874        return m_configuredRelationTypes;
875    }
876
877    /**
878     * Convenience method to get the initialized resource type instance for the given resource,
879     * with a fall back to special "unknown" resource types in case the resource type is not configured.<p>
880     *
881     * @param resource the resource to get the type for
882     *
883     * @return the initialized resource type instance for the given resource
884     */
885    public I_CmsResourceType getResourceType(CmsResource resource) {
886
887        I_CmsResourceType result = m_configuration.getResourceTypeById(resource.getTypeId());
888        if (result == null) {
889            // this resource type is unknown, return the default files instead
890            if (resource.isFolder()) {
891                // resource is a folder
892                if (m_restypeUnknownFolder != null) {
893                    result = m_restypeUnknownFolder;
894                } else {
895                    result = m_configuration.getResourceTypeByName(CmsResourceTypeFolder.getStaticTypeName());
896                }
897            } else {
898                // resource is a file
899                if (m_restypeUnknownFile != null) {
900                    result = m_restypeUnknownFile;
901                } else {
902                    result = m_configuration.getResourceTypeByName(CmsResourceTypeBinary.getStaticTypeName());
903                }
904            }
905        }
906        return result;
907    }
908
909    /**
910     * Returns the initialized resource type instance for the given id.<p>
911     *
912     * @param typeId the id of the resource type to get
913     *
914     * @return the initialized resource type instance for the given id
915     *
916     * @throws CmsLoaderException if no resource type is available for the given id
917     */
918    public I_CmsResourceType getResourceType(int typeId) throws CmsLoaderException {
919
920        I_CmsResourceType result = m_configuration.getResourceTypeById(typeId);
921        if (result == null) {
922            throw new CmsLoaderException(
923                Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_ID_REQ_1, new Integer(typeId)));
924        }
925        return result;
926    }
927
928    /**
929     * Returns the initialized resource type instance for the given resource type name.<p>
930     *
931     * @param typeName the name of the resource type to get
932     *
933     * @return the initialized resource type instance for the given name
934     *
935     * @throws CmsLoaderException if no resource type is available for the given name
936     */
937    public I_CmsResourceType getResourceType(String typeName) throws CmsLoaderException {
938
939        I_CmsResourceType result = m_configuration.getResourceTypeByName(typeName);
940        if (result != null) {
941            return result;
942        }
943        throw new CmsLoaderException(Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_NAME_REQ_1, typeName));
944    }
945
946    /**
947     * Returns the (unmodifiable) list with all initialized resource types.<p>
948     *
949     * @return the (unmodifiable) list with all initialized resource types
950     */
951    public List<I_CmsResourceType> getResourceTypes() {
952
953        return m_configuration.m_resourceTypeList;
954    }
955
956    /**
957     * Returns the (unmodifiable) list with all initialized resource types including unknown types.<p>
958     *
959     * @return the (unmodifiable) list with all initialized resource types including unknown types
960     */
961    public List<I_CmsResourceType> getResourceTypesWithUnknown() {
962
963        return m_configuration.m_resourceTypeListWithUnknown;
964    }
965
966    /**
967     * The configured default type for files when the resource type is missing.<p>
968     *
969     * @return the configured default type for files
970     */
971    public I_CmsResourceType getResTypeUnknownFile() {
972
973        return m_restypeUnknownFile;
974    }
975
976    /**
977     * The configured default type for folders when the resource type is missing.<p>
978     *
979     * @return The configured default type for folders
980     */
981    public I_CmsResourceType getResTypeUnknownFolder() {
982
983        return m_restypeUnknownFolder;
984    }
985
986    /**
987     * Returns a template loader facade for the given file.<p>
988     * @param cms the current OpenCms user context
989     * @param resource the requested file
990     * @param templateProperty the property to read for the template
991     *
992     * @return a resource loader facade for the given file
993     * @throws CmsException if something goes wrong
994     */
995    public CmsTemplateLoaderFacade getTemplateLoaderFacade(CmsObject cms, CmsResource resource, String templateProperty)
996    throws CmsException {
997
998        return getTemplateLoaderFacade(cms, null, resource, templateProperty);
999    }
1000
1001    /**
1002     * Returns a template loader facade for the given file.<p>
1003     * @param cms the current OpenCms user context
1004     * @param request the current request
1005     * @param resource the requested file
1006     * @param templateProperty the property to read for the template
1007     *
1008     * @return a resource loader facade for the given file
1009     * @throws CmsException if something goes wrong
1010     */
1011    public CmsTemplateLoaderFacade getTemplateLoaderFacade(
1012        CmsObject cms,
1013        HttpServletRequest request,
1014        CmsResource resource,
1015        String templateProperty)
1016    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 (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        // This only sets the CmsObject the first time it's called
1171        m_nameGenerator.setAdminCms(cms);
1172
1173        if (CmsLog.INIT.isInfoEnabled()) {
1174            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_LOADER_CONFIG_FINISHED_0));
1175        }
1176    }
1177
1178    /**
1179     * Loads the requested resource and writes the contents to the response stream.<p>
1180     *
1181     * @param req the current HTTP request
1182     * @param res the current HTTP response
1183     * @param cms the current OpenCms user context
1184     * @param resource the requested resource
1185     * @throws ServletException if something goes wrong
1186     * @throws IOException if something goes wrong
1187     * @throws CmsException if something goes wrong
1188     */
1189    public void loadResource(CmsObject cms, CmsResource resource, HttpServletRequest req, HttpServletResponse res)
1190    throws ServletException, IOException, CmsException {
1191
1192        res.setContentType(getMimeType(resource.getName(), cms.getRequestContext().getEncoding()));
1193        I_CmsResourceLoader loader = getLoader(resource);
1194        loader.load(cms, resource, req, res);
1195    }
1196
1197    /**
1198     * Checks if there is a resource type with a given name whose id matches the given id.<p>
1199     *
1200     * This will return 'false' if no resource type with the given name is registered.<p>
1201     *
1202     * @param name a resource type name
1203     * @param id a resource type id
1204     *
1205     * @return true if a matching resource type with the given name and id was found
1206     */
1207    public boolean matchResourceType(String name, int id) {
1208
1209        if (hasResourceType(name)) {
1210            try {
1211                return getResourceType(name).getTypeId() == id;
1212            } catch (Exception e) {
1213                // should never happen because we already checked with hasResourceType, still have to
1214                // catch it so the compiler is happy
1215                LOG.error(e.getLocalizedMessage(), e);
1216                return false;
1217            }
1218        } else {
1219            return false;
1220        }
1221    }
1222
1223    /**
1224     * Configures the URL name generator for XML contents.<p>
1225     *
1226     * @param nameGenerator the configured name generator class
1227     *
1228     * @throws CmsConfigurationException if something goes wrong
1229     */
1230    public void setNameGenerator(I_CmsFileNameGenerator nameGenerator) throws CmsConfigurationException {
1231
1232        if (m_frozen) {
1233            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
1234        }
1235        m_nameGenerator = nameGenerator;
1236
1237        if (CmsLog.INIT.isInfoEnabled()) {
1238            CmsLog.INIT.info(
1239                Messages.get().getBundle().key(Messages.INIT_SET_NAME_GENERATOR_1, nameGenerator.getClass().getName()));
1240        }
1241    }
1242
1243    /**
1244     * Sets the folder, the file and the XSD translator.<p>
1245     *
1246     * @param folderTranslator the folder translator to set
1247     * @param fileTranslator the file translator to set
1248     * @param xsdTranslator the XSD translator to set
1249     */
1250    public void setTranslators(
1251        CmsResourceTranslator folderTranslator,
1252        CmsResourceTranslator fileTranslator,
1253        CmsResourceTranslator xsdTranslator) {
1254
1255        m_folderTranslator = folderTranslator;
1256        m_fileTranslator = fileTranslator;
1257        m_xsdTranslator = xsdTranslator;
1258    }
1259
1260    /**
1261     * Shuts down this resource manage instance.<p>
1262     *
1263     * @throws Exception in case of errors during shutdown
1264     */
1265    public synchronized void shutDown() throws Exception {
1266
1267        Iterator<I_CmsResourceLoader> it = m_loaderList.iterator();
1268        while (it.hasNext()) {
1269            // destroy all resource loaders
1270            I_CmsResourceLoader loader = it.next();
1271            loader.destroy();
1272        }
1273
1274        m_loaderList = null;
1275        m_loaders = null;
1276        m_collectorNameMappings = null;
1277        m_mimeTypes = null;
1278        m_configuredMimeTypes = null;
1279        m_configuredRelationTypes = null;
1280        m_configuredHtmlConverters = null;
1281        m_htmlConverters = null;
1282
1283        if (CmsLog.INIT.isInfoEnabled()) {
1284            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName()));
1285        }
1286    }
1287
1288    /**
1289     * Gets the template name for a template resource, using a cache for efficiency.<p>
1290     *
1291     * @param cms the current CMS context
1292     * @param resource the template resource
1293     * @return the template name
1294     *
1295     * @throws CmsException if something goes wrong
1296     */
1297    private String getTemplateName(CmsObject cms, CmsResource resource) throws CmsException {
1298
1299        String templateName = (String)(m_templateNameCache.getCachedObject(cms, resource.getRootPath()));
1300        if (templateName == null) {
1301            CmsProperty nameProperty = cms.readPropertyObject(
1302                resource,
1303                CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS,
1304                false);
1305            String nameFromProperty = "";
1306            if (!nameProperty.isNullProperty()) {
1307                nameFromProperty = nameProperty.getValue();
1308            }
1309            m_templateNameCache.putCachedObject(cms, resource.getRootPath(), nameFromProperty);
1310            return nameFromProperty;
1311        } else {
1312            return templateName;
1313        }
1314    }
1315
1316    /**
1317     * Initialize the HTML converters.<p>
1318     *
1319     * HTML converters are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1320     *
1321     * For legacy reasons, the default JTidy HTML converter has to be loaded if no explicit HTML converters
1322     * are configured in the configuration file.<p>
1323     */
1324    private void initHtmlConverters() {
1325
1326        // check if any HTML converter configuration were found
1327        if (m_configuredHtmlConverters.size() == 0) {
1328            // no converters configured, add default JTidy converter configuration
1329            String classJTidy = CmsHtmlConverterJTidy.class.getName();
1330            m_configuredHtmlConverters.add(
1331                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_ENABLED, classJTidy, true));
1332            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_XHTML, classJTidy, true));
1333            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_WORD, classJTidy, true));
1334            m_configuredHtmlConverters.add(
1335                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_REPLACE_PARAGRAPHS, classJTidy, true));
1336        }
1337
1338        // initialize lookup map of configured HTML converters
1339        m_htmlConverters = new HashMap<String, String>(m_configuredHtmlConverters.size());
1340        for (Iterator<CmsHtmlConverterOption> i = m_configuredHtmlConverters.iterator(); i.hasNext();) {
1341            CmsHtmlConverterOption converterOption = i.next();
1342            m_htmlConverters.put(converterOption.getName(), converterOption.getClassName());
1343        }
1344    }
1345
1346    /**
1347     * Initialize the MIME types.<p>
1348     *
1349     * MIME types are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1350     *
1351     * For legacy reasons, the MIME types are also read from a file <code>"mimetypes.properties"</code>
1352     * that must be located in the default <code>"classes"</code> folder of the web application.<p>
1353     */
1354    private void initMimeTypes() {
1355
1356        // legacy MIME type initialization: try to read properties file
1357        Properties mimeTypes = new Properties();
1358        try {
1359            // first try: read MIME types from default package
1360            mimeTypes.load(getClass().getClassLoader().getResourceAsStream("mimetypes.properties"));
1361        } catch (Throwable t) {
1362            try {
1363                // second try: read MIME types from loader package (legacy reasons, there are no types by default)
1364                mimeTypes.load(
1365                    getClass().getClassLoader().getResourceAsStream("org/opencms/loader/mimetypes.properties"));
1366            } catch (Throwable t2) {
1367                if (LOG.isInfoEnabled()) {
1368                    LOG.info(
1369                        Messages.get().getBundle().key(
1370                            Messages.LOG_READ_MIMETYPES_FAILED_2,
1371                            "mimetypes.properties",
1372                            "org/opencms/loader/mimetypes.properties"));
1373                }
1374            }
1375        }
1376
1377        // initialize the Map with all available MIME types
1378        List<CmsMimeType> combinedMimeTypes = new ArrayList<CmsMimeType>(
1379            mimeTypes.size() + m_configuredMimeTypes.size());
1380        // first add all MIME types from the configuration
1381        combinedMimeTypes.addAll(m_configuredMimeTypes);
1382        // now add the MIME types from the properties
1383        Iterator<Map.Entry<Object, Object>> i = mimeTypes.entrySet().iterator();
1384        while (i.hasNext()) {
1385            Map.Entry<Object, Object> entry = i.next();
1386            CmsMimeType mimeType = new CmsMimeType(entry.getKey().toString(), entry.getValue().toString(), false);
1387            if (!combinedMimeTypes.contains(mimeType)) {
1388                // make sure no MIME types from the XML configuration are overwritten
1389                combinedMimeTypes.add(mimeType);
1390            }
1391        }
1392
1393        // create a lookup Map for the MIME types
1394        m_mimeTypes = new HashMap<String, String>(mimeTypes.size());
1395        Iterator<CmsMimeType> j = combinedMimeTypes.iterator();
1396        while (j.hasNext()) {
1397            CmsMimeType mimeType = j.next();
1398            m_mimeTypes.put(mimeType.getExtension(), mimeType.getType());
1399        }
1400
1401        if (CmsLog.INIT.isInfoEnabled()) {
1402            CmsLog.INIT.info(
1403                Messages.get().getBundle().key(Messages.INIT_NUM_MIMETYPES_1, new Integer(m_mimeTypes.size())));
1404        }
1405    }
1406
1407    /**
1408     * Adds a new resource type to the internal list of loaded resource types and initializes
1409     * options for the resource type.<p>
1410     *
1411     * @param resourceType the resource type to add
1412     * @param configuration the resource configuration
1413     */
1414    private synchronized void initResourceType(
1415        I_CmsResourceType resourceType,
1416        CmsResourceManagerConfiguration configuration) {
1417
1418        // add the loader to the internal list of loaders
1419        configuration.addResourceType(resourceType);
1420        if (CmsLog.INIT.isInfoEnabled()) {
1421            CmsLog.INIT.info(
1422                Messages.get().getBundle().key(
1423                    Messages.INIT_ADD_RESTYPE_3,
1424                    resourceType.getTypeName(),
1425                    new Integer(resourceType.getTypeId()),
1426                    resourceType.getClass().getName()));
1427        }
1428
1429        // add the mappings
1430        List<String> mappings = resourceType.getConfiguredMappings();
1431        Iterator<String> i = mappings.iterator();
1432        while (i.hasNext()) {
1433            String mapping = i.next();
1434            // only add this mapping if a mapping with this file extension does not
1435            // exist already
1436            if (!configuration.m_extensionMappings.containsKey(mapping)) {
1437                configuration.m_extensionMappings.put(mapping, resourceType.getTypeName());
1438                if (CmsLog.INIT.isInfoEnabled()) {
1439                    CmsLog.INIT.info(
1440                        Messages.get().getBundle().key(
1441                            Messages.INIT_MAP_RESTYPE_2,
1442                            mapping,
1443                            resourceType.getTypeName()));
1444                }
1445            }
1446        }
1447    }
1448
1449    /**
1450     * Initializes member variables required for storing the resource types.<p>
1451     *
1452     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1453     */
1454    private synchronized void initResourceTypes() throws CmsConfigurationException {
1455
1456        if (CmsLog.INIT.isInfoEnabled()) {
1457            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STARTING_LOADER_CONFIG_0));
1458        }
1459
1460        CmsResourceManagerConfiguration newConfiguration = new CmsResourceManagerConfiguration();
1461
1462        if (CmsLog.INIT.isInfoEnabled()) {
1463            CmsLog.INIT.info(
1464                Messages.get().getBundle().key(
1465                    Messages.INIT_ADD_RESTYPE_FROM_FILE_2,
1466                    new Integer(m_resourceTypesFromXml.size()),
1467                    CmsVfsConfiguration.DEFAULT_XML_FILE_NAME));
1468        }
1469
1470        // build a new resource type list from the resource types of the XML configuration
1471        Iterator<I_CmsResourceType> i;
1472        i = m_resourceTypesFromXml.iterator();
1473        while (i.hasNext()) {
1474            I_CmsResourceType resourceType = i.next();
1475            initResourceType(resourceType, newConfiguration);
1476        }
1477
1478        // add all resource types declared in the modules
1479        CmsModuleManager moduleManager = OpenCms.getModuleManager();
1480        if (moduleManager != null) {
1481            Iterator<String> modules = moduleManager.getModuleNames().iterator();
1482            while (modules.hasNext()) {
1483                CmsModule module = moduleManager.getModule(modules.next());
1484                if ((module != null) && (module.getResourceTypes().size() > 0)) {
1485                    // module contains resource types
1486                    if (CmsLog.INIT.isInfoEnabled()) {
1487                        CmsLog.INIT.info(
1488                            Messages.get().getBundle().key(
1489                                Messages.INIT_ADD_NUM_RESTYPES_FROM_MOD_2,
1490                                new Integer(module.getResourceTypes().size()),
1491                                module.getName()));
1492                    }
1493
1494                    Iterator<I_CmsResourceType> j = module.getResourceTypes().iterator();
1495                    while (j.hasNext()) {
1496                        I_CmsResourceType resourceType = j.next();
1497                        I_CmsResourceType conflictingType = null;
1498                        if (resourceType.getTypeId() == CmsResourceTypeUnknownFile.RESOURCE_TYPE_ID) {
1499                            // default unknown file resource type
1500                            if (m_restypeUnknownFile != null) {
1501                                // error: already set
1502                                conflictingType = m_restypeUnknownFile;
1503                            } else {
1504                                m_restypeUnknownFile = resourceType;
1505                                continue;
1506                            }
1507                        } else if (resourceType.getTypeId() == CmsResourceTypeUnknownFolder.RESOURCE_TYPE_ID) {
1508                            // default unknown folder resource type
1509                            if (m_restypeUnknownFolder != null) {
1510                                // error: already set
1511                                conflictingType = m_restypeUnknownFolder;
1512                            } else {
1513                                m_restypeUnknownFile = resourceType;
1514                                continue;
1515                            }
1516                        } else {
1517                            // normal resource types
1518                            conflictingType = newConfiguration.getResourceTypeById(resourceType.getTypeId());
1519                        }
1520                        if (conflictingType != null) {
1521                            throw new CmsConfigurationException(
1522                                Messages.get().container(
1523                                    Messages.ERR_CONFLICTING_MODULE_RESOURCE_TYPES_5,
1524                                    new Object[] {
1525                                        resourceType.getTypeName(),
1526                                        new Integer(resourceType.getTypeId()),
1527                                        module.getName(),
1528                                        conflictingType.getTypeName(),
1529                                        new Integer(conflictingType.getTypeId())}));
1530                        }
1531                        initResourceType(resourceType, newConfiguration);
1532                    }
1533                }
1534            }
1535        }
1536
1537        // freeze the current configuration
1538        newConfiguration.freeze(m_restypeUnknownFile, m_restypeUnknownFile);
1539        m_configuration = newConfiguration;
1540        m_frozen = true;
1541
1542        if (CmsLog.INIT.isInfoEnabled()) {
1543            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_RESOURCE_TYPE_INITIALIZED_0));
1544        }
1545    }
1546
1547    /**
1548     * Reads a template resource together with its name.<p>
1549     *
1550     * @param cms the current CMS context
1551     * @param path the template path
1552     *
1553     * @return the template together with its name, or null if the template couldn't be read
1554     */
1555    private NamedTemplate readTemplateWithName(CmsObject cms, String path) {
1556
1557        try {
1558            CmsResource resource = cms.readResource(path, CmsResourceFilter.IGNORE_EXPIRATION);
1559            String name = getTemplateName(cms, resource);
1560            return new NamedTemplate(resource, name);
1561        } catch (Exception e) {
1562            return null;
1563        }
1564    }
1565
1566}