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        if (m_nameGenerator == null) {
853            m_nameGenerator = new CmsDefaultFileNameGenerator();
854        }
855        return m_nameGenerator;
856    }
857
858    /**
859     * Returns an (unmodifiable) list of class names of all currently registered content collectors
860     * ({@link I_CmsResourceCollector} objects).<p>
861     *
862     * @return an (unmodifiable) list of class names of all currently registered content collectors
863     *      ({@link I_CmsResourceCollector} objects)
864     */
865    public List<I_CmsResourceCollector> getRegisteredContentCollectors() {
866
867        return m_collectors;
868    }
869
870    /**
871     * Returns an unmodifiable List of the configured {@link CmsRelationType} objects.<p>
872     *
873     * @return an unmodifiable List of the configured {@link CmsRelationType} objects
874     */
875    public List<CmsRelationType> getRelationTypes() {
876
877        return m_configuredRelationTypes;
878    }
879
880    /**
881     * Convenience method to get the initialized resource type instance for the given resource,
882     * with a fall back to special "unknown" resource types in case the resource type is not configured.<p>
883     *
884     * @param resource the resource to get the type for
885     *
886     * @return the initialized resource type instance for the given resource
887     */
888    public I_CmsResourceType getResourceType(CmsResource resource) {
889
890        I_CmsResourceType result = m_configuration.getResourceTypeById(resource.getTypeId());
891        if (result == null) {
892            // this resource type is unknown, return the default files instead
893            if (resource.isFolder()) {
894                // resource is a folder
895                if (m_restypeUnknownFolder != null) {
896                    result = m_restypeUnknownFolder;
897                } else {
898                    result = m_configuration.getResourceTypeByName(CmsResourceTypeFolder.getStaticTypeName());
899                }
900            } else {
901                // resource is a file
902                if (m_restypeUnknownFile != null) {
903                    result = m_restypeUnknownFile;
904                } else {
905                    result = m_configuration.getResourceTypeByName(CmsResourceTypeBinary.getStaticTypeName());
906                }
907            }
908        }
909        return result;
910    }
911
912    /**
913     * Returns the initialized resource type instance for the given id.<p>
914     *
915     * @param typeId the id of the resource type to get
916     *
917     * @return the initialized resource type instance for the given id
918     *
919     * @throws CmsLoaderException if no resource type is available for the given id
920     */
921    public I_CmsResourceType getResourceType(int typeId) throws CmsLoaderException {
922
923        I_CmsResourceType result = m_configuration.getResourceTypeById(typeId);
924        if (result == null) {
925            throw new CmsLoaderException(
926                Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_ID_REQ_1, new Integer(typeId)));
927        }
928        return result;
929    }
930
931    /**
932     * Returns the initialized resource type instance for the given resource type name.<p>
933     *
934     * @param typeName the name of the resource type to get
935     *
936     * @return the initialized resource type instance for the given name
937     *
938     * @throws CmsLoaderException if no resource type is available for the given name
939     */
940    public I_CmsResourceType getResourceType(String typeName) throws CmsLoaderException {
941
942        I_CmsResourceType result = m_configuration.getResourceTypeByName(typeName);
943        if (result != null) {
944            return result;
945        }
946        throw new CmsLoaderException(Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_NAME_REQ_1, typeName));
947    }
948
949    /**
950     * Returns the (unmodifiable) list with all initialized resource types.<p>
951     *
952     * @return the (unmodifiable) list with all initialized resource types
953     */
954    public List<I_CmsResourceType> getResourceTypes() {
955
956        return m_configuration.m_resourceTypeList;
957    }
958
959    /**
960     * Returns the (unmodifiable) list with all initialized resource types including unknown types.<p>
961     *
962     * @return the (unmodifiable) list with all initialized resource types including unknown types
963     */
964    public List<I_CmsResourceType> getResourceTypesWithUnknown() {
965
966        return m_configuration.m_resourceTypeListWithUnknown;
967    }
968
969    /**
970     * The configured default type for files when the resource type is missing.<p>
971     *
972     * @return the configured default type for files
973     */
974    public I_CmsResourceType getResTypeUnknownFile() {
975
976        return m_restypeUnknownFile;
977    }
978
979    /**
980     * The configured default type for folders when the resource type is missing.<p>
981     *
982     * @return The configured default type for folders
983     */
984    public I_CmsResourceType getResTypeUnknownFolder() {
985
986        return m_restypeUnknownFolder;
987    }
988
989    /**
990     * Returns a template loader facade for the given file.<p>
991     * @param cms the current OpenCms user context
992     * @param resource the requested file
993     * @param templateProperty the property to read for the template
994     *
995     * @return a resource loader facade for the given file
996     * @throws CmsException if something goes wrong
997     */
998    public CmsTemplateLoaderFacade getTemplateLoaderFacade(CmsObject cms, CmsResource resource, String templateProperty)
999    throws CmsException {
1000
1001        return getTemplateLoaderFacade(cms, null, resource, templateProperty);
1002    }
1003
1004    /**
1005     * Returns a template loader facade for the given file.<p>
1006     * @param cms the current OpenCms user context
1007     * @param request the current request
1008     * @param resource the requested file
1009     * @param templateProperty the property to read for the template
1010     *
1011     * @return a resource loader facade for the given file
1012     * @throws CmsException if something goes wrong
1013     */
1014    public CmsTemplateLoaderFacade getTemplateLoaderFacade(
1015        CmsObject cms,
1016        HttpServletRequest request,
1017        CmsResource resource,
1018        String templateProperty)
1019    throws CmsException {
1020
1021        String templateProp = cms.readPropertyObject(resource, templateProperty, true).getValue();
1022        CmsTemplateContext templateContext = null;
1023        String templateName = null;
1024        if (templateProp == null) {
1025
1026            // use default template, if template is not set
1027            templateProp = DEFAULT_TEMPLATE;
1028            NamedTemplate namedTemplate = readTemplateWithName(cms, templateProp);
1029            if (namedTemplate == null) {
1030                // no template property defined, this is a must for facade loaders
1031                throw new CmsLoaderException(
1032                    Messages.get().container(Messages.ERR_NONDEF_PROP_2, templateProperty, cms.getSitePath(resource)));
1033            }
1034            templateName = namedTemplate.getName();
1035        } else {
1036            if (CmsTemplateContextManager.hasPropertyPrefix(templateProp)) {
1037                templateContext = OpenCms.getTemplateContextManager().getTemplateContext(
1038                    templateProp,
1039                    cms,
1040                    request,
1041                    resource);
1042                if (templateContext != null) {
1043                    templateProp = templateContext.getTemplatePath();
1044                }
1045            }
1046            NamedTemplate namedTemplate = readTemplateWithName(cms, templateProp);
1047            if (namedTemplate == null) {
1048                namedTemplate = readTemplateWithName(cms, DEFAULT_TEMPLATE);
1049                if (namedTemplate != null) {
1050                    templateProp = DEFAULT_TEMPLATE;
1051                    templateName = namedTemplate.getName();
1052                }
1053            } else {
1054                templateName = namedTemplate.getName();
1055            }
1056        }
1057        CmsResource template = cms.readFile(templateProp, CmsResourceFilter.IGNORE_EXPIRATION);
1058        CmsTemplateLoaderFacade result = new CmsTemplateLoaderFacade(getLoader(template), resource, template);
1059        result.setTemplateContext(templateContext);
1060        result.setTemplateName(templateName);
1061        return result;
1062
1063    }
1064
1065    /**
1066     * Returns the XSD translator.<p>
1067     *
1068     * @return the XSD translator
1069     */
1070    public CmsResourceTranslator getXsdTranslator() {
1071
1072        return m_xsdTranslator;
1073    }
1074
1075    /**
1076     * Checks if an initialized resource type instance equal to the given resource type is available.<p>
1077     *
1078     * @param type the resource type to check
1079     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1080     *
1081     * @see #getResourceType(String)
1082     * @see #getResourceType(int)
1083     */
1084    public boolean hasResourceType(I_CmsResourceType type) {
1085
1086        return hasResourceType(type.getTypeName());
1087    }
1088
1089    /**
1090     * Checks if an initialized resource type instance for the given resource type is is available.<p>
1091     *
1092     * @param typeId the id of the resource type to check
1093     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1094     *
1095     * @see #getResourceType(int)
1096     *
1097     * @deprecated
1098     * Use {@link #hasResourceType(I_CmsResourceType)} or {@link #hasResourceType(I_CmsResourceType)} instead.
1099     * Resource types should always be referenced either by its type class (preferred) or by type name.
1100     * Use of int based resource type references will be discontinued in a future OpenCms release.
1101     */
1102    @Deprecated
1103    public boolean hasResourceType(int typeId) {
1104
1105        return m_configuration.getResourceTypeById(typeId) != null;
1106    }
1107
1108    /**
1109     * Checks if an initialized resource type instance for the given resource type name is available.<p>
1110     *
1111     * @param typeName the name of the resource type to check
1112     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1113     *
1114     * @see #getResourceType(String)
1115     */
1116    public boolean hasResourceType(String typeName) {
1117
1118        return m_configuration.getResourceTypeByName(typeName) != null;
1119    }
1120
1121    /**
1122     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration()
1123     *
1124     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1125     */
1126    public void initConfiguration() throws CmsConfigurationException {
1127
1128        if (CmsLog.INIT.isInfoEnabled()) {
1129            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_LOADER_CONFIG_FINISHED_0));
1130        }
1131
1132        m_resourceTypesFromXml = Collections.unmodifiableList(m_resourceTypesFromXml);
1133        m_loaderList = Collections.unmodifiableList(m_loaderList);
1134        Collections.sort(m_configuredMimeTypes);
1135        m_configuredMimeTypes = Collections.unmodifiableList(m_configuredMimeTypes);
1136        m_configuredRelationTypes = Collections.unmodifiableList(m_configuredRelationTypes);
1137
1138        // initialize the HTML converters
1139        initHtmlConverters();
1140        m_configuredHtmlConverters = Collections.unmodifiableList(m_configuredHtmlConverters);
1141
1142        // initialize the resource types
1143        initResourceTypes();
1144        // initialize the MIME types
1145        initMimeTypes();
1146    }
1147
1148    /**
1149     * Initializes all additional resource types stored in the modules.<p>
1150     *
1151     * @param cms an initialized OpenCms user context with "module manager" role permissions
1152     *
1153     * @throws CmsRoleViolationException in case the provided OpenCms user context did not have "module manager" role permissions
1154     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1155     */
1156    public synchronized void initialize(CmsObject cms) throws CmsRoleViolationException, CmsConfigurationException {
1157
1158        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
1159            // some simple test cases don't require this check
1160            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
1161        }
1162
1163        // initialize the resource types
1164        initResourceTypes();
1165
1166        // call initialize method on all resource types
1167        Iterator<I_CmsResourceType> i = m_configuration.m_resourceTypeList.iterator();
1168        while (i.hasNext()) {
1169            I_CmsResourceType type = i.next();
1170            type.initialize(cms);
1171        }
1172
1173        m_nameGenerator.setAdminCms(cms);
1174
1175        if (CmsLog.INIT.isInfoEnabled()) {
1176            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_LOADER_CONFIG_FINISHED_0));
1177        }
1178    }
1179
1180    /**
1181     * Loads the requested resource and writes the contents to the response stream.<p>
1182     *
1183     * @param req the current HTTP request
1184     * @param res the current HTTP response
1185     * @param cms the current OpenCms user context
1186     * @param resource the requested resource
1187     * @throws ServletException if something goes wrong
1188     * @throws IOException if something goes wrong
1189     * @throws CmsException if something goes wrong
1190     */
1191    public void loadResource(CmsObject cms, CmsResource resource, HttpServletRequest req, HttpServletResponse res)
1192    throws ServletException, IOException, CmsException {
1193
1194        res.setContentType(getMimeType(resource.getName(), cms.getRequestContext().getEncoding()));
1195        I_CmsResourceLoader loader = getLoader(resource);
1196        loader.load(cms, resource, req, res);
1197    }
1198
1199    /**
1200     * Checks if there is a resource type with a given name whose id matches the given id.<p>
1201     *
1202     * This will return 'false' if no resource type with the given name is registered.<p>
1203     *
1204     * @param name a resource type name
1205     * @param id a resource type id
1206     *
1207     * @return true if a matching resource type with the given name and id was found
1208     */
1209    public boolean matchResourceType(String name, int id) {
1210
1211        if (hasResourceType(name)) {
1212            try {
1213                return getResourceType(name).getTypeId() == id;
1214            } catch (Exception e) {
1215                // should never happen because we already checked with hasResourceType, still have to
1216                // catch it so the compiler is happy
1217                LOG.error(e.getLocalizedMessage(), e);
1218                return false;
1219            }
1220        } else {
1221            return false;
1222        }
1223    }
1224
1225    /**
1226     * Configures the URL name generator for XML contents.<p>
1227     *
1228     * @param nameGenerator the configured name generator class
1229     *
1230     * @throws CmsConfigurationException if something goes wrong
1231     */
1232    public void setNameGenerator(I_CmsFileNameGenerator nameGenerator) throws CmsConfigurationException {
1233
1234        if (m_frozen) {
1235            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
1236        }
1237        m_nameGenerator = nameGenerator;
1238
1239        if (CmsLog.INIT.isInfoEnabled()) {
1240            CmsLog.INIT.info(
1241                Messages.get().getBundle().key(Messages.INIT_SET_NAME_GENERATOR_1, nameGenerator.getClass().getName()));
1242        }
1243    }
1244
1245    /**
1246     * Sets the folder, the file and the XSD translator.<p>
1247     *
1248     * @param folderTranslator the folder translator to set
1249     * @param fileTranslator the file translator to set
1250     * @param xsdTranslator the XSD translator to set
1251     */
1252    public void setTranslators(
1253        CmsResourceTranslator folderTranslator,
1254        CmsResourceTranslator fileTranslator,
1255        CmsResourceTranslator xsdTranslator) {
1256
1257        m_folderTranslator = folderTranslator;
1258        m_fileTranslator = fileTranslator;
1259        m_xsdTranslator = xsdTranslator;
1260    }
1261
1262    /**
1263     * Shuts down this resource manage instance.<p>
1264     *
1265     * @throws Exception in case of errors during shutdown
1266     */
1267    public synchronized void shutDown() throws Exception {
1268
1269        Iterator<I_CmsResourceLoader> it = m_loaderList.iterator();
1270        while (it.hasNext()) {
1271            // destroy all resource loaders
1272            I_CmsResourceLoader loader = it.next();
1273            loader.destroy();
1274        }
1275
1276        m_loaderList = null;
1277        m_loaders = null;
1278        m_collectorNameMappings = null;
1279        m_mimeTypes = null;
1280        m_configuredMimeTypes = null;
1281        m_configuredRelationTypes = null;
1282        m_configuredHtmlConverters = null;
1283        m_htmlConverters = null;
1284
1285        if (CmsLog.INIT.isInfoEnabled()) {
1286            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName()));
1287        }
1288    }
1289
1290    /**
1291     * Gets the template name for a template resource, using a cache for efficiency.<p>
1292     *
1293     * @param cms the current CMS context
1294     * @param resource the template resource
1295     * @return the template name
1296     *
1297     * @throws CmsException if something goes wrong
1298     */
1299    private String getTemplateName(CmsObject cms, CmsResource resource) throws CmsException {
1300
1301        String templateName = (String)(m_templateNameCache.getCachedObject(cms, resource.getRootPath()));
1302        if (templateName == null) {
1303            CmsProperty nameProperty = cms.readPropertyObject(
1304                resource,
1305                CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS,
1306                false);
1307            String nameFromProperty = "";
1308            if (!nameProperty.isNullProperty()) {
1309                nameFromProperty = nameProperty.getValue();
1310            }
1311            m_templateNameCache.putCachedObject(cms, resource.getRootPath(), nameFromProperty);
1312            return nameFromProperty;
1313        } else {
1314            return templateName;
1315        }
1316    }
1317
1318    /**
1319     * Initialize the HTML converters.<p>
1320     *
1321     * HTML converters are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1322     *
1323     * For legacy reasons, the default JTidy HTML converter has to be loaded if no explicit HTML converters
1324     * are configured in the configuration file.<p>
1325     */
1326    private void initHtmlConverters() {
1327
1328        // check if any HTML converter configuration were found
1329        if (m_configuredHtmlConverters.size() == 0) {
1330            // no converters configured, add default JTidy converter configuration
1331            String classJTidy = CmsHtmlConverterJTidy.class.getName();
1332            m_configuredHtmlConverters.add(
1333                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_ENABLED, classJTidy, true));
1334            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_XHTML, classJTidy, true));
1335            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_WORD, classJTidy, true));
1336            m_configuredHtmlConverters.add(
1337                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_REPLACE_PARAGRAPHS, classJTidy, true));
1338        }
1339
1340        // initialize lookup map of configured HTML converters
1341        m_htmlConverters = new HashMap<String, String>(m_configuredHtmlConverters.size());
1342        for (Iterator<CmsHtmlConverterOption> i = m_configuredHtmlConverters.iterator(); i.hasNext();) {
1343            CmsHtmlConverterOption converterOption = i.next();
1344            m_htmlConverters.put(converterOption.getName(), converterOption.getClassName());
1345        }
1346    }
1347
1348    /**
1349     * Initialize the MIME types.<p>
1350     *
1351     * MIME types are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1352     *
1353     * For legacy reasons, the MIME types are also read from a file <code>"mimetypes.properties"</code>
1354     * that must be located in the default <code>"classes"</code> folder of the web application.<p>
1355     */
1356    private void initMimeTypes() {
1357
1358        // legacy MIME type initialization: try to read properties file
1359        Properties mimeTypes = new Properties();
1360        try {
1361            // first try: read MIME types from default package
1362            mimeTypes.load(getClass().getClassLoader().getResourceAsStream("mimetypes.properties"));
1363        } catch (Throwable t) {
1364            try {
1365                // second try: read MIME types from loader package (legacy reasons, there are no types by default)
1366                mimeTypes.load(
1367                    getClass().getClassLoader().getResourceAsStream("org/opencms/loader/mimetypes.properties"));
1368            } catch (Throwable t2) {
1369                if (LOG.isInfoEnabled()) {
1370                    LOG.info(
1371                        Messages.get().getBundle().key(
1372                            Messages.LOG_READ_MIMETYPES_FAILED_2,
1373                            "mimetypes.properties",
1374                            "org/opencms/loader/mimetypes.properties"));
1375                }
1376            }
1377        }
1378
1379        // initialize the Map with all available MIME types
1380        List<CmsMimeType> combinedMimeTypes = new ArrayList<CmsMimeType>(
1381            mimeTypes.size() + m_configuredMimeTypes.size());
1382        // first add all MIME types from the configuration
1383        combinedMimeTypes.addAll(m_configuredMimeTypes);
1384        // now add the MIME types from the properties
1385        Iterator<Map.Entry<Object, Object>> i = mimeTypes.entrySet().iterator();
1386        while (i.hasNext()) {
1387            Map.Entry<Object, Object> entry = i.next();
1388            CmsMimeType mimeType = new CmsMimeType(entry.getKey().toString(), entry.getValue().toString(), false);
1389            if (!combinedMimeTypes.contains(mimeType)) {
1390                // make sure no MIME types from the XML configuration are overwritten
1391                combinedMimeTypes.add(mimeType);
1392            }
1393        }
1394
1395        // create a lookup Map for the MIME types
1396        m_mimeTypes = new HashMap<String, String>(mimeTypes.size());
1397        Iterator<CmsMimeType> j = combinedMimeTypes.iterator();
1398        while (j.hasNext()) {
1399            CmsMimeType mimeType = j.next();
1400            m_mimeTypes.put(mimeType.getExtension(), mimeType.getType());
1401        }
1402
1403        if (CmsLog.INIT.isInfoEnabled()) {
1404            CmsLog.INIT.info(
1405                Messages.get().getBundle().key(Messages.INIT_NUM_MIMETYPES_1, new Integer(m_mimeTypes.size())));
1406        }
1407    }
1408
1409    /**
1410     * Adds a new resource type to the internal list of loaded resource types and initializes
1411     * options for the resource type.<p>
1412     *
1413     * @param resourceType the resource type to add
1414     * @param configuration the resource configuration
1415     */
1416    private synchronized void initResourceType(
1417        I_CmsResourceType resourceType,
1418        CmsResourceManagerConfiguration configuration) {
1419
1420        // add the loader to the internal list of loaders
1421        configuration.addResourceType(resourceType);
1422        if (CmsLog.INIT.isInfoEnabled()) {
1423            CmsLog.INIT.info(
1424                Messages.get().getBundle().key(
1425                    Messages.INIT_ADD_RESTYPE_3,
1426                    resourceType.getTypeName(),
1427                    new Integer(resourceType.getTypeId()),
1428                    resourceType.getClass().getName()));
1429        }
1430
1431        // add the mappings
1432        List<String> mappings = resourceType.getConfiguredMappings();
1433        Iterator<String> i = mappings.iterator();
1434        while (i.hasNext()) {
1435            String mapping = i.next();
1436            // only add this mapping if a mapping with this file extension does not
1437            // exist already
1438            if (!configuration.m_extensionMappings.containsKey(mapping)) {
1439                configuration.m_extensionMappings.put(mapping, resourceType.getTypeName());
1440                if (CmsLog.INIT.isInfoEnabled()) {
1441                    CmsLog.INIT.info(
1442                        Messages.get().getBundle().key(
1443                            Messages.INIT_MAP_RESTYPE_2,
1444                            mapping,
1445                            resourceType.getTypeName()));
1446                }
1447            }
1448        }
1449    }
1450
1451    /**
1452     * Initializes member variables required for storing the resource types.<p>
1453     *
1454     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1455     */
1456    private synchronized void initResourceTypes() throws CmsConfigurationException {
1457
1458        if (CmsLog.INIT.isInfoEnabled()) {
1459            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STARTING_LOADER_CONFIG_0));
1460        }
1461
1462        CmsResourceManagerConfiguration newConfiguration = new CmsResourceManagerConfiguration();
1463
1464        if (CmsLog.INIT.isInfoEnabled()) {
1465            CmsLog.INIT.info(
1466                Messages.get().getBundle().key(
1467                    Messages.INIT_ADD_RESTYPE_FROM_FILE_2,
1468                    new Integer(m_resourceTypesFromXml.size()),
1469                    CmsVfsConfiguration.DEFAULT_XML_FILE_NAME));
1470        }
1471
1472        // build a new resource type list from the resource types of the XML configuration
1473        Iterator<I_CmsResourceType> i;
1474        i = m_resourceTypesFromXml.iterator();
1475        while (i.hasNext()) {
1476            I_CmsResourceType resourceType = i.next();
1477            initResourceType(resourceType, newConfiguration);
1478        }
1479
1480        // add all resource types declared in the modules
1481        CmsModuleManager moduleManager = OpenCms.getModuleManager();
1482        if (moduleManager != null) {
1483            Iterator<String> modules = moduleManager.getModuleNames().iterator();
1484            while (modules.hasNext()) {
1485                CmsModule module = moduleManager.getModule(modules.next());
1486                if ((module != null) && (module.getResourceTypes().size() > 0)) {
1487                    // module contains resource types
1488                    if (CmsLog.INIT.isInfoEnabled()) {
1489                        CmsLog.INIT.info(
1490                            Messages.get().getBundle().key(
1491                                Messages.INIT_ADD_NUM_RESTYPES_FROM_MOD_2,
1492                                new Integer(module.getResourceTypes().size()),
1493                                module.getName()));
1494                    }
1495
1496                    Iterator<I_CmsResourceType> j = module.getResourceTypes().iterator();
1497                    while (j.hasNext()) {
1498                        I_CmsResourceType resourceType = j.next();
1499                        I_CmsResourceType conflictingType = null;
1500                        if (resourceType.getTypeId() == CmsResourceTypeUnknownFile.RESOURCE_TYPE_ID) {
1501                            // default unknown file resource type
1502                            if (m_restypeUnknownFile != null) {
1503                                // error: already set
1504                                conflictingType = m_restypeUnknownFile;
1505                            } else {
1506                                m_restypeUnknownFile = resourceType;
1507                                continue;
1508                            }
1509                        } else if (resourceType.getTypeId() == CmsResourceTypeUnknownFolder.RESOURCE_TYPE_ID) {
1510                            // default unknown folder resource type
1511                            if (m_restypeUnknownFolder != null) {
1512                                // error: already set
1513                                conflictingType = m_restypeUnknownFolder;
1514                            } else {
1515                                m_restypeUnknownFile = resourceType;
1516                                continue;
1517                            }
1518                        } else {
1519                            // normal resource types
1520                            conflictingType = newConfiguration.getResourceTypeById(resourceType.getTypeId());
1521                        }
1522                        if (conflictingType != null) {
1523                            throw new CmsConfigurationException(
1524                                Messages.get().container(
1525                                    Messages.ERR_CONFLICTING_MODULE_RESOURCE_TYPES_5,
1526                                    new Object[] {
1527                                        resourceType.getTypeName(),
1528                                        new Integer(resourceType.getTypeId()),
1529                                        module.getName(),
1530                                        conflictingType.getTypeName(),
1531                                        new Integer(conflictingType.getTypeId())}));
1532                        }
1533                        initResourceType(resourceType, newConfiguration);
1534                    }
1535                }
1536            }
1537        }
1538
1539        // freeze the current configuration
1540        newConfiguration.freeze(m_restypeUnknownFile, m_restypeUnknownFile);
1541        m_configuration = newConfiguration;
1542        m_frozen = true;
1543
1544        if (CmsLog.INIT.isInfoEnabled()) {
1545            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_RESOURCE_TYPE_INITIALIZED_0));
1546        }
1547    }
1548
1549    /**
1550     * Reads a template resource together with its name.<p>
1551     *
1552     * @param cms the current CMS context
1553     * @param path the template path
1554     *
1555     * @return the template together with its name, or null if the template couldn't be read
1556     */
1557    private NamedTemplate readTemplateWithName(CmsObject cms, String path) {
1558
1559        try {
1560            CmsResource resource = cms.readResource(path, CmsResourceFilter.IGNORE_EXPIRATION);
1561            String name = getTemplateName(cms, resource);
1562            return new NamedTemplate(resource, name);
1563        } catch (Exception e) {
1564            return null;
1565        }
1566    }
1567
1568}