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