001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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                    Integer.valueOf(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                        Integer.valueOf(resourceType.getTypeId()),
587                        conflictingType.getTypeName(),
588                        Integer.valueOf(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 ignoreMissingSchema = resType.getConfiguration().getString(
606                    I_CmsResourceType.PARAM_IGNORE_MISSING_SCHEMA,
607                    "false");
608                String schema = null;
609                try {
610                    schema = ((CmsResourceTypeXmlContent)resType).getSchema();
611                    if (schema != null) {
612                        CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, schema);
613
614                        CmsDefaultSet<String> allowedContexts = contentDefinition.getContentHandler().getAllowedTemplates();
615                        result.put(resType.getTypeName(), allowedContexts);
616                    } else {
617                        LOG.info(
618                            "No schema for XML type " + resType.getTypeName() + " / " + resType.getClass().getName());
619                    }
620                } catch (Exception e) {
621                    if (Boolean.parseBoolean(ignoreMissingSchema)) {
622                        continue;
623                    }
624                    LOG.error(
625                        "Error in getAllowedContextMap, schema="
626                            + schema
627                            + ", type="
628                            + resType.getTypeName()
629                            + ", "
630                            + e.getLocalizedMessage(),
631                        e);
632
633                }
634            }
635        }
636        return result;
637    }
638
639    /**
640     * Returns the configured content collector with the given name, or <code>null</code> if
641     * no collector with this name is configured.<p>
642     *
643     * @param collectorName the name of the collector to get
644     * @return the configured content collector with the given name
645     */
646    public I_CmsResourceCollector getContentCollector(String collectorName) {
647
648        return m_collectorNameMappings.get(collectorName);
649    }
650
651    /**
652     * Returns the default resource type for the given resource name, using the
653     * configured resource type file extensions.<p>
654     *
655     * In case the given name does not map to a configured resource type,
656     * {@link CmsResourceTypePlain} is returned.<p>
657     *
658     * This is only required (and should <i>not</i> be used otherwise) when
659     * creating a new resource automatically during file upload or synchronization.
660     * Only in this case, the file type for the new resource is determined using this method.
661     * Otherwise the resource type is <i>always</i> stored as part of the resource,
662     * and is <i>not</i> related to the file name.<p>
663     *
664     * @param resourcename the resource name to look up the resource type for
665     *
666     * @return the default resource type for the given resource name
667     *
668     * @throws CmsException if something goes wrong
669     */
670    public I_CmsResourceType getDefaultTypeForName(String resourcename) throws CmsException {
671
672        String typeName = null;
673        String suffix = null;
674        if (CmsStringUtil.isNotEmpty(resourcename)) {
675            int pos = resourcename.lastIndexOf('.');
676            if (pos >= 0) {
677                suffix = resourcename.substring(pos);
678                if (CmsStringUtil.isNotEmpty(suffix)) {
679                    suffix = suffix.toLowerCase();
680                    typeName = m_configuration.m_extensionMappings.get(suffix);
681
682                }
683            }
684        }
685
686        if (typeName == null) {
687            // use default type "plain"
688            typeName = CmsResourceTypePlain.getStaticTypeName();
689        }
690
691        if (CmsLog.INIT.isDebugEnabled()) {
692            CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_GET_RESTYPE_2, typeName, suffix));
693        }
694        // look up and return the resource type
695        return getResourceType(typeName);
696    }
697
698    /**
699     * Returns the file extensions (suffixes) mappings to resource types.<p>
700     *
701     * @return a Map with all known file extensions as keys and their resource types as values.
702     */
703    public Map<String, String> getExtensionMapping() {
704
705        return m_configuration.m_extensionMappings;
706    }
707
708    /**
709     * Returns the file translator.<p>
710     *
711     * @return the file translator
712     */
713    public CmsResourceTranslator getFileTranslator() {
714
715        return m_fileTranslator;
716    }
717
718    /**
719     * Returns the folder translator.<p>
720     *
721     * @return the folder translator
722     */
723    public CmsResourceTranslator getFolderTranslator() {
724
725        return m_folderTranslator;
726    }
727
728    /**
729     * Returns the matching HTML converter class name for the specified option name.<p>
730     *
731     * @param name the name of the option that should trigger the HTML converter class
732     *
733     * @return the matching HTML converter class name for the specified option name or <code>null</code> if no match is found
734     */
735    public String getHtmlConverter(String name) {
736
737        return m_htmlConverters.get(name);
738    }
739
740    /**
741     * Returns an unmodifiable List of the configured {@link CmsHtmlConverterOption} objects.<p>
742     *
743     * @return an unmodifiable List of the configured {@link CmsHtmlConverterOption} objects
744     */
745    public List<CmsHtmlConverterOption> getHtmlConverters() {
746
747        return m_configuredHtmlConverters;
748    }
749
750    /**
751     * Returns the loader class instance for a given resource.<p>
752     *
753     * @param resource the resource
754     * @return the appropriate loader class instance
755     * @throws CmsLoaderException if something goes wrong
756     */
757    public I_CmsResourceLoader getLoader(CmsResource resource) throws CmsLoaderException {
758
759        return getLoader(getResourceType(resource.getTypeId()).getLoaderId());
760    }
761
762    /**
763     * Returns the loader class instance for the given loader id.<p>
764     *
765     * @param id the id of the loader to return
766     * @return the loader class instance for the given loader id
767     */
768    public I_CmsResourceLoader getLoader(int id) {
769
770        return m_loaders[id];
771    }
772
773    /**
774     * Returns the (unmodifiable array) list with all initialized resource loaders.<p>
775     *
776     * @return the (unmodifiable array) list with all initialized resource loaders
777     */
778    public List<I_CmsResourceLoader> getLoaders() {
779
780        return m_loaderList;
781    }
782
783    /**
784     * Returns the MIME type for a specified file name.<p>
785     *
786     * If an encoding parameter that is not <code>null</code> is provided,
787     * the returned MIME type is extended with a <code>; charset={encoding}</code> setting.<p>
788     *
789     * If no MIME type for the given filename can be determined, the
790     * default <code>{@link #MIMETYPE_HTML}</code> is used.<p>
791     *
792     * @param filename the file name to check the MIME type for
793     * @param encoding the default encoding (charset) in case of MIME types is of type "text"
794     *
795     * @return the MIME type for a specified file
796     */
797    public String getMimeType(String filename, String encoding) {
798
799        return getMimeType(filename, encoding, MIMETYPE_HTML);
800    }
801
802    /**
803     * Returns the MIME type for a specified file name.<p>
804     *
805     * If an encoding parameter that is not <code>null</code> is provided,
806     * the returned MIME type is extended with a <code>; charset={encoding}</code> setting.<p>
807     *
808     * If no MIME type for the given filename can be determined, the
809     * provided default is used.<p>
810     *
811     * @param filename the file name to check the MIME type for
812     * @param encoding the default encoding (charset) in case of MIME types is of type "text"
813     * @param defaultMimeType the default MIME type to use if no matching type for the filename is found
814     *
815     * @return the MIME type for a specified file
816     */
817    public String getMimeType(String filename, String encoding, String defaultMimeType) {
818
819        String mimeType = null;
820        int lastDot = filename.lastIndexOf('.');
821        // check the MIME type for the file extension
822        if ((lastDot > 0) && (lastDot < (filename.length() - 1))) {
823            mimeType = m_mimeTypes.get(filename.substring(lastDot).toLowerCase(Locale.ENGLISH));
824        }
825        if (mimeType == null) {
826            mimeType = defaultMimeType;
827            if (mimeType == null) {
828                // no default MIME type was provided
829                return null;
830            }
831        }
832        StringBuffer result = new StringBuffer(mimeType);
833        if ((encoding != null)
834            && (mimeType.startsWith("text") || mimeType.endsWith("javascript"))
835            && (mimeType.indexOf("charset") == -1)) {
836            result.append("; charset=");
837            result.append(encoding);
838        }
839        return result.toString();
840    }
841
842    /**
843     * Returns an unmodifiable List of the configured {@link CmsMimeType} objects.<p>
844     *
845     * @return an unmodifiable List of the configured {@link CmsMimeType} objects
846     */
847    public List<CmsMimeType> getMimeTypes() {
848
849        return m_configuredMimeTypes;
850    }
851
852    /**
853     * Returns the name generator for XML content file names.<p>
854     *
855     * @return the name generator for XML content file names.
856     */
857    public I_CmsFileNameGenerator getNameGenerator() {
858
859        return m_nameGenerator;
860    }
861
862    /**
863     * Returns an (unmodifiable) list of class names of all currently registered content collectors
864     * ({@link I_CmsResourceCollector} objects).<p>
865     *
866     * @return an (unmodifiable) list of class names of all currently registered content collectors
867     *      ({@link I_CmsResourceCollector} objects)
868     */
869    public List<I_CmsResourceCollector> getRegisteredContentCollectors() {
870
871        return m_collectors;
872    }
873
874    /**
875     * Returns an unmodifiable List of the configured {@link CmsRelationType} objects.<p>
876     *
877     * @return an unmodifiable List of the configured {@link CmsRelationType} objects
878     */
879    public List<CmsRelationType> getRelationTypes() {
880
881        return m_configuredRelationTypes;
882    }
883
884    /**
885     * Convenience method to get the initialized resource type instance for the given resource,
886     * with a fall back to special "unknown" resource types in case the resource type is not configured.<p>
887     *
888     * @param resource the resource to get the type for
889     *
890     * @return the initialized resource type instance for the given resource
891     */
892    public I_CmsResourceType getResourceType(CmsResource resource) {
893
894        I_CmsResourceType result = m_configuration.getResourceTypeById(resource.getTypeId());
895        if (result == null) {
896            // this resource type is unknown, return the default files instead
897            if (resource.isFolder()) {
898                // resource is a folder
899                if (m_restypeUnknownFolder != null) {
900                    result = m_restypeUnknownFolder;
901                } else {
902                    result = m_configuration.getResourceTypeByName(CmsResourceTypeFolder.getStaticTypeName());
903                }
904            } else {
905                // resource is a file
906                if (m_restypeUnknownFile != null) {
907                    result = m_restypeUnknownFile;
908                } else {
909                    result = m_configuration.getResourceTypeByName(CmsResourceTypeBinary.getStaticTypeName());
910                }
911            }
912        }
913        return result;
914    }
915
916    /**
917     * Returns the initialized resource type instance for the given id.<p>
918     *
919     * @param typeId the id of the resource type to get
920     *
921     * @return the initialized resource type instance for the given id
922     *
923     * @throws CmsLoaderException if no resource type is available for the given id
924     */
925    public I_CmsResourceType getResourceType(int typeId) throws CmsLoaderException {
926
927        I_CmsResourceType result = m_configuration.getResourceTypeById(typeId);
928        if (result == null) {
929            throw new CmsLoaderException(
930                Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_ID_REQ_1, Integer.valueOf(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)
1023    throws CmsException {
1024
1025        String templateProp = cms.readPropertyObject(resource, templateProperty, true).getValue();
1026        CmsTemplateContext templateContext = null;
1027        String templateName = null;
1028        if (templateProp == null) {
1029
1030            // use default template, if template is not set
1031            templateProp = DEFAULT_TEMPLATE;
1032            NamedTemplate namedTemplate = readTemplateWithName(cms, templateProp);
1033            if (namedTemplate == null) {
1034                // no template property defined, this is a must for facade loaders
1035                throw new CmsLoaderException(
1036                    Messages.get().container(Messages.ERR_NONDEF_PROP_2, templateProperty, cms.getSitePath(resource)));
1037            }
1038            templateName = namedTemplate.getName();
1039        } else {
1040            if (CmsTemplateContextManager.hasPropertyPrefix(templateProp)) {
1041                templateContext = OpenCms.getTemplateContextManager().getTemplateContext(
1042                    templateProp,
1043                    cms,
1044                    request,
1045                    resource);
1046                if (templateContext != null) {
1047                    templateProp = templateContext.getTemplatePath();
1048                }
1049            }
1050            NamedTemplate namedTemplate = readTemplateWithName(cms, templateProp);
1051            if (namedTemplate == null) {
1052                namedTemplate = readTemplateWithName(cms, DEFAULT_TEMPLATE);
1053                if (namedTemplate != null) {
1054                    templateProp = DEFAULT_TEMPLATE;
1055                    templateName = namedTemplate.getName();
1056                }
1057            } else {
1058                templateName = namedTemplate.getName();
1059            }
1060        }
1061        CmsResource template = cms.readFile(templateProp, CmsResourceFilter.IGNORE_EXPIRATION);
1062        CmsTemplateLoaderFacade result = new CmsTemplateLoaderFacade(getLoader(template), resource, template);
1063        result.setTemplateContext(templateContext);
1064        result.setTemplateName(templateName);
1065        return result;
1066
1067    }
1068
1069    /**
1070     * Returns the XSD translator.<p>
1071     *
1072     * @return the XSD translator
1073     */
1074    public CmsResourceTranslator getXsdTranslator() {
1075
1076        return m_xsdTranslator;
1077    }
1078
1079    /**
1080     * Checks if an initialized resource type instance equal to the given resource type is available.<p>
1081     *
1082     * @param type the resource type to check
1083     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1084     *
1085     * @see #getResourceType(String)
1086     * @see #getResourceType(int)
1087     */
1088    public boolean hasResourceType(I_CmsResourceType type) {
1089
1090        return hasResourceType(type.getTypeName());
1091    }
1092
1093    /**
1094     * Checks if an initialized resource type instance for the given resource type is is available.<p>
1095     *
1096     * @param typeId the id of the resource type to check
1097     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1098     *
1099     * @see #getResourceType(int)
1100     *
1101     * @deprecated
1102     * Use {@link #hasResourceType(I_CmsResourceType)} or {@link #hasResourceType(I_CmsResourceType)} instead.
1103     * Resource types should always be referenced either by its type class (preferred) or by type name.
1104     * Use of int based resource type references will be discontinued in a future OpenCms release.
1105     */
1106    @Deprecated
1107    public boolean hasResourceType(int typeId) {
1108
1109        return m_configuration.getResourceTypeById(typeId) != null;
1110    }
1111
1112    /**
1113     * Checks if an initialized resource type instance for the given resource type name is available.<p>
1114     *
1115     * @param typeName the name of the resource type to check
1116     * @return <code>true</code> if such a resource type has been configured, <code>false</code> otherwise
1117     *
1118     * @see #getResourceType(String)
1119     */
1120    public boolean hasResourceType(String typeName) {
1121
1122        return m_configuration.getResourceTypeByName(typeName) != null;
1123    }
1124
1125    /**
1126     * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration()
1127     *
1128     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1129     */
1130    public void initConfiguration() throws CmsConfigurationException {
1131
1132        if (CmsLog.INIT.isInfoEnabled()) {
1133            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_LOADER_CONFIG_FINISHED_0));
1134        }
1135
1136        m_resourceTypesFromXml = Collections.unmodifiableList(m_resourceTypesFromXml);
1137        m_loaderList = Collections.unmodifiableList(m_loaderList);
1138        Collections.sort(m_configuredMimeTypes);
1139        m_configuredMimeTypes = Collections.unmodifiableList(m_configuredMimeTypes);
1140        m_configuredRelationTypes = Collections.unmodifiableList(m_configuredRelationTypes);
1141
1142        // initialize the HTML converters
1143        initHtmlConverters();
1144        m_configuredHtmlConverters = Collections.unmodifiableList(m_configuredHtmlConverters);
1145
1146        // initialize the resource types
1147        initResourceTypes();
1148        // initialize the MIME types
1149        initMimeTypes();
1150    }
1151
1152    /**
1153     * Initializes all additional resource types stored in the modules.<p>
1154     *
1155     * @param cms an initialized OpenCms user context with "module manager" role permissions
1156     *
1157     * @throws CmsRoleViolationException in case the provided OpenCms user context did not have "module manager" role permissions
1158     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1159     */
1160    public synchronized void initialize(CmsObject cms) throws CmsRoleViolationException, CmsConfigurationException {
1161
1162        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
1163            // some simple test cases don't require this check
1164            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
1165        }
1166
1167        // initialize the resource types
1168        initResourceTypes();
1169
1170        // call initialize method on all resource types
1171        Iterator<I_CmsResourceType> i = m_configuration.m_resourceTypeList.iterator();
1172        while (i.hasNext()) {
1173            I_CmsResourceType type = i.next();
1174            type.initialize(cms);
1175        }
1176
1177        // This only sets the CmsObject the first time it's called
1178        m_nameGenerator.setAdminCms(cms);
1179
1180        if (CmsLog.INIT.isInfoEnabled()) {
1181            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_LOADER_CONFIG_FINISHED_0));
1182        }
1183    }
1184
1185    /**
1186     * Loads the requested resource and writes the contents to the response stream.<p>
1187     *
1188     * @param req the current HTTP request
1189     * @param res the current HTTP response
1190     * @param cms the current OpenCms user context
1191     * @param resource the requested resource
1192     * @throws ServletException if something goes wrong
1193     * @throws IOException if something goes wrong
1194     * @throws CmsException if something goes wrong
1195     */
1196    public void loadResource(CmsObject cms, CmsResource resource, HttpServletRequest req, HttpServletResponse res)
1197    throws ServletException, IOException, CmsException {
1198
1199        res.setContentType(getMimeType(resource.getName(), cms.getRequestContext().getEncoding()));
1200        I_CmsResourceLoader loader = getLoader(resource);
1201        loader.load(cms, resource, req, res);
1202    }
1203
1204    /**
1205     * Checks if there is a resource type with a given name whose id matches the given id.<p>
1206     *
1207     * This will return 'false' if no resource type with the given name is registered.<p>
1208     *
1209     * @param name a resource type name
1210     * @param id a resource type id
1211     *
1212     * @return true if a matching resource type with the given name and id was found
1213     */
1214    public boolean matchResourceType(String name, int id) {
1215
1216        if (hasResourceType(name)) {
1217            try {
1218                return getResourceType(name).getTypeId() == id;
1219            } catch (Exception e) {
1220                // should never happen because we already checked with hasResourceType, still have to
1221                // catch it so the compiler is happy
1222                LOG.error(e.getLocalizedMessage(), e);
1223                return false;
1224            }
1225        } else {
1226            return false;
1227        }
1228    }
1229
1230    /**
1231     * Configures the URL name generator for XML contents.<p>
1232     *
1233     * @param nameGenerator the configured name generator class
1234     *
1235     * @throws CmsConfigurationException if something goes wrong
1236     */
1237    public void setNameGenerator(I_CmsFileNameGenerator nameGenerator) throws CmsConfigurationException {
1238
1239        if (m_frozen) {
1240            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_NO_CONFIG_AFTER_STARTUP_0));
1241        }
1242        m_nameGenerator = nameGenerator;
1243
1244        if (CmsLog.INIT.isInfoEnabled()) {
1245            CmsLog.INIT.info(
1246                Messages.get().getBundle().key(Messages.INIT_SET_NAME_GENERATOR_1, nameGenerator.getClass().getName()));
1247        }
1248    }
1249
1250    /**
1251     * Sets the folder, the file and the XSD translator.<p>
1252     *
1253     * @param folderTranslator the folder translator to set
1254     * @param fileTranslator the file translator to set
1255     * @param xsdTranslator the XSD translator to set
1256     */
1257    public void setTranslators(
1258        CmsResourceTranslator folderTranslator,
1259        CmsResourceTranslator fileTranslator,
1260        CmsResourceTranslator xsdTranslator) {
1261
1262        m_folderTranslator = folderTranslator;
1263        m_fileTranslator = fileTranslator;
1264        m_xsdTranslator = xsdTranslator;
1265    }
1266
1267    /**
1268     * Shuts down this resource manage instance.<p>
1269     *
1270     * @throws Exception in case of errors during shutdown
1271     */
1272    public synchronized void shutDown() throws Exception {
1273
1274        Iterator<I_CmsResourceLoader> it = m_loaderList.iterator();
1275        while (it.hasNext()) {
1276            // destroy all resource loaders
1277            I_CmsResourceLoader loader = it.next();
1278            loader.destroy();
1279        }
1280
1281        m_loaderList = null;
1282        m_loaders = null;
1283        m_collectorNameMappings = null;
1284        m_mimeTypes = null;
1285        m_configuredMimeTypes = null;
1286        m_configuredRelationTypes = null;
1287        m_configuredHtmlConverters = null;
1288        m_htmlConverters = null;
1289
1290        if (CmsLog.INIT.isInfoEnabled()) {
1291            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName()));
1292        }
1293    }
1294
1295    /**
1296     * Gets the template name for a template resource, using a cache for efficiency.<p>
1297     *
1298     * @param cms the current CMS context
1299     * @param resource the template resource
1300     * @return the template name
1301     *
1302     * @throws CmsException if something goes wrong
1303     */
1304    private String getTemplateName(CmsObject cms, CmsResource resource) throws CmsException {
1305
1306        String templateName = (String)(m_templateNameCache.getCachedObject(cms, resource.getRootPath()));
1307        if (templateName == null) {
1308            CmsProperty nameProperty = cms.readPropertyObject(
1309                resource,
1310                CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS,
1311                false);
1312            String nameFromProperty = "";
1313            if (!nameProperty.isNullProperty()) {
1314                nameFromProperty = nameProperty.getValue();
1315            }
1316            m_templateNameCache.putCachedObject(cms, resource.getRootPath(), nameFromProperty);
1317            return nameFromProperty;
1318        } else {
1319            return templateName;
1320        }
1321    }
1322
1323    /**
1324     * Initialize the HTML converters.<p>
1325     *
1326     * HTML converters are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1327     *
1328     * For legacy reasons, the default JTidy HTML converter has to be loaded if no explicit HTML converters
1329     * are configured in the configuration file.<p>
1330     */
1331    private void initHtmlConverters() {
1332
1333        // check if any HTML converter configuration were found
1334        if (m_configuredHtmlConverters.size() == 0) {
1335            // no converters configured, add default JTidy converter configuration
1336            String classJTidy = CmsHtmlConverterJTidy.class.getName();
1337            m_configuredHtmlConverters.add(
1338                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_ENABLED, classJTidy, true));
1339            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_XHTML, classJTidy, true));
1340            m_configuredHtmlConverters.add(new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_WORD, classJTidy, true));
1341            m_configuredHtmlConverters.add(
1342                new CmsHtmlConverterOption(CmsHtmlConverter.PARAM_REPLACE_PARAGRAPHS, classJTidy, true));
1343        }
1344
1345        // initialize lookup map of configured HTML converters
1346        m_htmlConverters = new HashMap<String, String>(m_configuredHtmlConverters.size());
1347        for (Iterator<CmsHtmlConverterOption> i = m_configuredHtmlConverters.iterator(); i.hasNext();) {
1348            CmsHtmlConverterOption converterOption = i.next();
1349            m_htmlConverters.put(converterOption.getName(), converterOption.getClassName());
1350        }
1351    }
1352
1353    /**
1354     * Initialize the MIME types.<p>
1355     *
1356     * MIME types are configured in the OpenCms <code>opencms-vfs.xml</code> configuration file.<p>
1357     *
1358     * For legacy reasons, the MIME types are also read from a file <code>"mimetypes.properties"</code>
1359     * that must be located in the default <code>"classes"</code> folder of the web application.<p>
1360     */
1361    private void initMimeTypes() {
1362
1363        // legacy MIME type initialization: try to read properties file
1364        Properties mimeTypes = new Properties();
1365        try {
1366            // first try: read MIME types from default package
1367            mimeTypes.load(getClass().getClassLoader().getResourceAsStream("mimetypes.properties"));
1368        } catch (Throwable t) {
1369            try {
1370                // second try: read MIME types from loader package (legacy reasons, there are no types by default)
1371                mimeTypes.load(
1372                    getClass().getClassLoader().getResourceAsStream("org/opencms/loader/mimetypes.properties"));
1373            } catch (Throwable t2) {
1374                if (LOG.isInfoEnabled()) {
1375                    LOG.info(
1376                        Messages.get().getBundle().key(
1377                            Messages.LOG_READ_MIMETYPES_FAILED_2,
1378                            "mimetypes.properties",
1379                            "org/opencms/loader/mimetypes.properties"));
1380                }
1381            }
1382        }
1383
1384        // initialize the Map with all available MIME types
1385        List<CmsMimeType> combinedMimeTypes = new ArrayList<CmsMimeType>(
1386            mimeTypes.size() + m_configuredMimeTypes.size());
1387        // first add all MIME types from the configuration
1388        combinedMimeTypes.addAll(m_configuredMimeTypes);
1389        // now add the MIME types from the properties
1390        Iterator<Map.Entry<Object, Object>> i = mimeTypes.entrySet().iterator();
1391        while (i.hasNext()) {
1392            Map.Entry<Object, Object> entry = i.next();
1393            CmsMimeType mimeType = new CmsMimeType(entry.getKey().toString(), entry.getValue().toString(), false);
1394            if (!combinedMimeTypes.contains(mimeType)) {
1395                // make sure no MIME types from the XML configuration are overwritten
1396                combinedMimeTypes.add(mimeType);
1397            }
1398        }
1399
1400        // create a lookup Map for the MIME types
1401        m_mimeTypes = new HashMap<String, String>(mimeTypes.size());
1402        Iterator<CmsMimeType> j = combinedMimeTypes.iterator();
1403        while (j.hasNext()) {
1404            CmsMimeType mimeType = j.next();
1405            m_mimeTypes.put(mimeType.getExtension(), mimeType.getType());
1406        }
1407
1408        if (CmsLog.INIT.isInfoEnabled()) {
1409            CmsLog.INIT.info(
1410                Messages.get().getBundle().key(Messages.INIT_NUM_MIMETYPES_1, Integer.valueOf(m_mimeTypes.size())));
1411        }
1412    }
1413
1414    /**
1415     * Adds a new resource type to the internal list of loaded resource types and initializes
1416     * options for the resource type.<p>
1417     *
1418     * @param resourceType the resource type to add
1419     * @param configuration the resource configuration
1420     */
1421    private synchronized void initResourceType(
1422        I_CmsResourceType resourceType,
1423        CmsResourceManagerConfiguration configuration) {
1424
1425        // add the loader to the internal list of loaders
1426        configuration.addResourceType(resourceType);
1427        if (CmsLog.INIT.isInfoEnabled()) {
1428            CmsLog.INIT.info(
1429                Messages.get().getBundle().key(
1430                    Messages.INIT_ADD_RESTYPE_3,
1431                    resourceType.getTypeName(),
1432                    Integer.valueOf(resourceType.getTypeId()),
1433                    resourceType.getClass().getName()));
1434        }
1435
1436        // add the mappings
1437        List<String> mappings = resourceType.getConfiguredMappings();
1438        Iterator<String> i = mappings.iterator();
1439        while (i.hasNext()) {
1440            String mapping = i.next();
1441            // only add this mapping if a mapping with this file extension does not
1442            // exist already
1443            if (!configuration.m_extensionMappings.containsKey(mapping)) {
1444                configuration.m_extensionMappings.put(mapping, resourceType.getTypeName());
1445                if (CmsLog.INIT.isInfoEnabled()) {
1446                    CmsLog.INIT.info(
1447                        Messages.get().getBundle().key(
1448                            Messages.INIT_MAP_RESTYPE_2,
1449                            mapping,
1450                            resourceType.getTypeName()));
1451                }
1452            }
1453        }
1454    }
1455
1456    /**
1457     * Initializes member variables required for storing the resource types.<p>
1458     *
1459     * @throws CmsConfigurationException in case of duplicate resource types in the configuration
1460     */
1461    private synchronized void initResourceTypes() throws CmsConfigurationException {
1462
1463        if (CmsLog.INIT.isInfoEnabled()) {
1464            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STARTING_LOADER_CONFIG_0));
1465        }
1466
1467        CmsResourceManagerConfiguration newConfiguration = new CmsResourceManagerConfiguration();
1468
1469        if (CmsLog.INIT.isInfoEnabled()) {
1470            CmsLog.INIT.info(
1471                Messages.get().getBundle().key(
1472                    Messages.INIT_ADD_RESTYPE_FROM_FILE_2,
1473                    Integer.valueOf(m_resourceTypesFromXml.size()),
1474                    CmsVfsConfiguration.DEFAULT_XML_FILE_NAME));
1475        }
1476
1477        // build a new resource type list from the resource types of the XML configuration
1478        Iterator<I_CmsResourceType> i;
1479        i = m_resourceTypesFromXml.iterator();
1480        while (i.hasNext()) {
1481            I_CmsResourceType resourceType = i.next();
1482            initResourceType(resourceType, newConfiguration);
1483        }
1484
1485        // add all resource types declared in the modules
1486        CmsModuleManager moduleManager = OpenCms.getModuleManager();
1487        if (moduleManager != null) {
1488            Iterator<String> modules = moduleManager.getModuleNames().iterator();
1489            while (modules.hasNext()) {
1490                CmsModule module = moduleManager.getModule(modules.next());
1491                if ((module != null) && (module.getResourceTypes().size() > 0)) {
1492                    // module contains resource types
1493                    if (CmsLog.INIT.isInfoEnabled()) {
1494                        CmsLog.INIT.info(
1495                            Messages.get().getBundle().key(
1496                                Messages.INIT_ADD_NUM_RESTYPES_FROM_MOD_2,
1497                                Integer.valueOf(module.getResourceTypes().size()),
1498                                module.getName()));
1499                    }
1500
1501                    Iterator<I_CmsResourceType> j = module.getResourceTypes().iterator();
1502                    while (j.hasNext()) {
1503                        I_CmsResourceType resourceType = j.next();
1504                        I_CmsResourceType conflictingType = null;
1505                        if (resourceType.getTypeId() == CmsResourceTypeUnknownFile.RESOURCE_TYPE_ID) {
1506                            // default unknown file resource type
1507                            if (m_restypeUnknownFile != null) {
1508                                // error: already set
1509                                conflictingType = m_restypeUnknownFile;
1510                            } else {
1511                                m_restypeUnknownFile = resourceType;
1512                                continue;
1513                            }
1514                        } else if (resourceType.getTypeId() == CmsResourceTypeUnknownFolder.RESOURCE_TYPE_ID) {
1515                            // default unknown folder resource type
1516                            if (m_restypeUnknownFolder != null) {
1517                                // error: already set
1518                                conflictingType = m_restypeUnknownFolder;
1519                            } else {
1520                                m_restypeUnknownFile = resourceType;
1521                                continue;
1522                            }
1523                        } else {
1524                            // normal resource types
1525                            conflictingType = newConfiguration.getResourceTypeById(resourceType.getTypeId());
1526                        }
1527                        if (conflictingType != null) {
1528                            throw new CmsConfigurationException(
1529                                Messages.get().container(
1530                                    Messages.ERR_CONFLICTING_MODULE_RESOURCE_TYPES_5,
1531                                    new Object[] {
1532                                        resourceType.getTypeName(),
1533                                        Integer.valueOf(resourceType.getTypeId()),
1534                                        module.getName(),
1535                                        conflictingType.getTypeName(),
1536                                        Integer.valueOf(conflictingType.getTypeId())}));
1537                        }
1538                        initResourceType(resourceType, newConfiguration);
1539                    }
1540                }
1541            }
1542        }
1543
1544        // freeze the current configuration
1545        newConfiguration.freeze(m_restypeUnknownFile, m_restypeUnknownFile);
1546        m_configuration = newConfiguration;
1547        m_frozen = true;
1548
1549        if (CmsLog.INIT.isInfoEnabled()) {
1550            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_RESOURCE_TYPE_INITIALIZED_0));
1551        }
1552    }
1553
1554    /**
1555     * Reads a template resource together with its name.<p>
1556     *
1557     * @param cms the current CMS context
1558     * @param path the template path
1559     *
1560     * @return the template together with its name, or null if the template couldn't be read
1561     */
1562    private NamedTemplate readTemplateWithName(CmsObject cms, String path) {
1563
1564        try {
1565            CmsResource resource = cms.readResource(path, CmsResourceFilter.IGNORE_EXPIRATION);
1566            String name = getTemplateName(cms, resource);
1567            return new NamedTemplate(resource, name);
1568        } catch (Exception e) {
1569            return null;
1570        }
1571    }
1572
1573}