001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.workplace.editors;
029
030import org.opencms.main.CmsLog;
031import org.opencms.util.CmsStringUtil;
032import org.opencms.xml.CmsXmlException;
033import org.opencms.xml.CmsXmlUtils;
034
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Map;
040import java.util.regex.Pattern;
041import java.util.regex.PatternSyntaxException;
042
043import org.apache.commons.logging.Log;
044
045import org.dom4j.Document;
046import org.dom4j.Element;
047
048/**
049 * Single editor configuration object.<p>
050 *
051 * Holds all necessary information about an OpenCms editor which is stored in the
052 * "editor_configuration.xml" file in each editor folder.<p>
053 *
054 * Provides methods to get the editor information for the editor manager.<p>
055 *
056 * @since 6.0.0
057 */
058public class CmsWorkplaceEditorConfiguration {
059
060    /** Name of the root document node. */
061    public static final String DOCUMENT_NODE = "editor";
062
063    /** Name of the name attribute. */
064    protected static final String A_NAME = "name";
065
066    /** Name of the single user agent node. */
067    protected static final String N_AGENT = "agent";
068
069    /** Name of the resource type class node. */
070    protected static final String N_CLASS = "class";
071
072    /** Name of the editor label node. */
073    protected static final String N_LABEL = "label";
074
075    /** Name of the resource type subnode mapto. */
076    protected static final String N_MAPTO = "mapto";
077
078    /** Name of the resource type subnode name. */
079    protected static final String N_NAME = "name";
080
081    /** Name of the param node. */
082    protected static final String N_PARAM = "param";
083
084    /** Name of the params node. */
085    protected static final String N_PARAMS = "params";
086
087    /** Name of the resource type subnode ranking. */
088    protected static final String N_RANKING = "ranking";
089
090    /** Name of the resourcetypes node. */
091    protected static final String N_RESOURCETYPES = "resourcetypes";
092
093    /** Name of the resource type node. */
094    protected static final String N_TYPE = "type";
095
096    /** Name of the useragents node. */
097    protected static final String N_USERAGENTS = "useragents";
098
099    /** Name of the widgeteditor node. */
100    protected static final String N_WIDGETEDITOR = "widgeteditor";
101
102    /** The log object for this class. */
103    private static final Log LOG = CmsLog.getLog(CmsWorkplaceEditorConfiguration.class);
104
105    /** The browser patterns. */
106    private List<Pattern> m_browserPattern;
107
108    /** The editor label. */
109    private String m_editorLabel;
110
111    /** The editor URI. */
112    private String m_editorUri;
113
114    /** The name of the configuration (usually the name of the folder under /system/workplace/editors). */
115    private String m_name;
116
117    /** Additional parameters for the editor. */
118    private Map<String, String> m_parameters = new HashMap<String, String>();
119
120    /** The resource types. */
121    private Map<String, String[]> m_resTypes;
122
123    /** The user agents. */
124    private List<String> m_userAgentsRegEx;
125
126    /** The valid configuration flag. */
127    private boolean m_validConfiguration;
128
129    /** The widget editor. */
130    private String m_widgetEditor;
131
132    /**
133     * Constructor with xml data String.<p>
134     *
135     * @param xmlData the XML data String containing the information about the editor
136     * @param editorUri the editor workplace URI
137     * @param name the editor configuration name
138     */
139    public CmsWorkplaceEditorConfiguration(byte[] xmlData, String editorUri, String name) {
140
141        setValidConfiguration(true);
142        try {
143            m_name = name;
144            initialize(CmsXmlUtils.unmarshalHelper(xmlData, null), editorUri);
145
146        } catch (CmsXmlException e) {
147            // xml String could not be parsed
148            logConfigurationError(Messages.get().getBundle().key(Messages.ERR_XML_PARSE_0), e);
149        }
150    }
151
152    /**
153     * Returns the list of compiled browser patterns.<p>
154     *
155     * @return the list of compiled browser patterns
156     */
157    public List<Pattern> getBrowserPattern() {
158
159        return m_browserPattern;
160    }
161
162    /**
163     * Returns the editor label key used for the localized nice name.<p>
164     *
165     * @return the editor label key used for the localized nice name
166     */
167    public String getEditorLabel() {
168
169        return m_editorLabel;
170    }
171
172    /**
173     * Returns the editor workplace URI.<p>
174     *
175     * @return the editor workplace URI
176     */
177    public String getEditorUri() {
178
179        return m_editorUri;
180    }
181
182    /**
183     * Returns the mapping for the given resource type.<p>
184     *
185     * @param resourceType the resource type name to check
186     * @return the mapping or null, if no mapping is specified
187     */
188    public String getMappingForResourceType(String resourceType) {
189
190        String[] resourceTypeParams = getResourceTypes().get(resourceType);
191        if (resourceTypeParams == null) {
192            return null;
193        } else {
194            return resourceTypeParams[1];
195        }
196    }
197
198    /**
199     * Gets the name of the editor configuration (usually the folder name under /system/workplace/editors).<p>
200     *
201     * @return the name of the editor configuration
202     */
203    public String getName() {
204
205        return m_name;
206    }
207
208    /**
209     * Gets the map of additional editor parameters.<p>
210     *
211     * @return the editor parameter map
212     */
213    public Map<String, String> getParameters() {
214
215        return m_parameters;
216    }
217
218    /**
219     * Returns the ranking value for the given resource type.<p>
220     *
221     * @param resourceType the current resource type
222     * @return the ranking (the higher the better)
223     */
224    public float getRankingForResourceType(String resourceType) {
225
226        String[] resourceTypeParams = getResourceTypes().get(resourceType);
227        if (resourceTypeParams == null) {
228            return -1.0f;
229        } else {
230            return Float.parseFloat(resourceTypeParams[0]);
231        }
232    }
233
234    /**
235     * Returns the valid resource types of the editor.<p>
236     *
237     * A single map item has the resource type name as key,
238     * the value is a String array with two entries:
239     * <ul>
240     * <li>Entry 0: the ranking for the resource type</li>
241     * <li>Entry 1: the mapping to another resource type or null</li>
242     * </ul><p>
243     *
244     * @return the valid resource types of the editor
245     */
246    public Map<String, String[]> getResourceTypes() {
247
248        return m_resTypes;
249    }
250
251    /**
252     * Returns the valid user agents regular expressions of the editor.<p>
253     *
254     * @return the valid user agents regular expressions of the editor
255     */
256    public List<String> getUserAgentsRegEx() {
257
258        return m_userAgentsRegEx;
259    }
260
261    /**
262     * Returns the widget editor class for rich text editing.<p>
263     *
264     * @return the widget editor class for rich text editing
265     */
266    public String getWidgetEditor() {
267
268        return m_widgetEditor;
269    }
270
271    /**
272     * Returns if the current configuration is valid.<p>
273     *
274     * @return true if no configuration errors were found, otherwise false
275     */
276    public boolean isValidConfiguration() {
277
278        return m_validConfiguration;
279    }
280
281    /**
282     * Returns if the editor is usable as a widget editor for rich text editing.<p>
283     *
284     * @return true if the editor is usable as a widget editor for rich text editing, otherwise false
285     */
286    public boolean isWidgetEditor() {
287
288        return CmsStringUtil.isNotEmpty(m_widgetEditor);
289    }
290
291    /**
292     * Tests if the current browser is matching the configuration.<p>
293     *
294     * @param currentBrowser the users browser String to test
295     * @return true if the browser matches the configuration, otherwise false
296     */
297    public boolean matchesBrowser(String currentBrowser) {
298
299        if (currentBrowser == null) {
300            return false;
301        }
302        for (int i = 0; i < getBrowserPattern().size(); i++) {
303            boolean matches = getBrowserPattern().get(i).matcher(currentBrowser.trim()).matches();
304            if (matches) {
305                if (LOG.isDebugEnabled()) {
306                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_BROWSER_MATCHES_CONFIG_1, currentBrowser));
307                }
308                return true;
309            }
310        }
311        return false;
312    }
313
314    /**
315     * Returns if the configuration is suitable for the given resource type.<p>
316     *
317     * @param resourceType the resource type to check
318     * @return true if the configuration matches the resource type
319     */
320    public boolean matchesResourceType(String resourceType) {
321
322        return m_resTypes.containsKey(resourceType);
323    }
324
325    /**
326     * Initializes all member variables.<p>
327     *
328     * @param document the XML configuration document
329     * @param editorUri the editor workplace URI
330     */
331    @SuppressWarnings("unchecked")
332    private void initialize(Document document, String editorUri) {
333
334        m_parameters.clear();
335
336        // get the root element of the configuration
337        Element rootElement = document.getRootElement();
338
339        // set the label of the editor
340        setEditorLabel(rootElement.elementText(N_LABEL));
341
342        // set the widget editor class if available
343        String widgetClass = rootElement.elementText(N_WIDGETEDITOR);
344        if (CmsStringUtil.isNotEmpty(widgetClass)) {
345            setWidgetEditor(widgetClass);
346        }
347
348        // set the URI of the editor
349        setEditorUri(editorUri);
350
351        // create the map of valid resource types
352        Iterator<Element> i = rootElement.element(N_RESOURCETYPES).elementIterator(N_TYPE);
353        Map<String, String[]> resTypes = new HashMap<String, String[]>();
354        while (i.hasNext()) {
355            Element currentType = i.next();
356            float ranking;
357            String name = currentType.elementText(N_NAME);
358            if (CmsStringUtil.isEmpty(name)) {
359                logConfigurationError(Messages.get().getBundle().key(Messages.ERR_INVALID_RESTYPE_NAME_0), null);
360                continue;
361            }
362            try {
363                ranking = Float.parseFloat(currentType.elementText(N_RANKING));
364            } catch (Throwable t) {
365                logConfigurationError(Messages.get().getBundle().key(Messages.ERR_INVALID_RESTYPE_RANKING_1, name), t);
366                continue;
367            }
368            String mapTo = currentType.elementText(N_MAPTO);
369            if (CmsStringUtil.isEmpty(mapTo)) {
370                mapTo = null;
371            }
372            resTypes.put(name, new String[] {"" + ranking, mapTo});
373        }
374        // add the additional resource types
375        i = rootElement.element(N_RESOURCETYPES).elementIterator(N_CLASS);
376        while (i.hasNext()) {
377            Element currentClass = i.next();
378            String name = currentClass.elementText(N_NAME);
379            List<String> assignedTypes = new ArrayList<String>();
380            try {
381                // get the editor type matcher class
382                I_CmsEditorTypeMatcher matcher = (I_CmsEditorTypeMatcher)Class.forName(name).newInstance();
383                assignedTypes = matcher.getAdditionalResourceTypes();
384            } catch (Throwable t) {
385                logConfigurationError(Messages.get().getBundle().key(Messages.ERR_INVALID_RESTYPE_CLASS_1, name), t);
386                continue;
387            }
388            float ranking;
389            try {
390                ranking = Float.parseFloat(currentClass.elementText(N_RANKING));
391            } catch (Throwable t) {
392                logConfigurationError(Messages.get().getBundle().key(Messages.ERR_INVALID_RESTYPE_RANKING_1, name), t);
393                continue;
394            }
395            String mapTo = currentClass.elementText(N_MAPTO);
396            if ("".equals(mapTo)) {
397                mapTo = null;
398            }
399            // now loop through all types found and add them
400            Iterator<String> j = assignedTypes.iterator();
401            while (j.hasNext()) {
402                String typeName = j.next();
403                resTypes.put(typeName, new String[] {"" + ranking, mapTo});
404            }
405        }
406
407        setResourceTypes(resTypes);
408
409        // create the list of user agents & compiled patterns for editor
410        i = document.getRootElement().element(N_USERAGENTS).elementIterator(N_AGENT);
411        List<Pattern> pattern = new ArrayList<Pattern>();
412        List<String> userAgents = new ArrayList<String>();
413        while (i.hasNext()) {
414            Element currentAgent = i.next();
415            String agentName = currentAgent.getText();
416            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(agentName)) {
417                userAgents.add(agentName);
418                try {
419                    pattern.add(Pattern.compile(agentName));
420                } catch (PatternSyntaxException e) {
421                    logConfigurationError(
422                        Messages.get().getBundle().key(Messages.ERR_COMPILE_EDITOR_REGEX_1, agentName),
423                        e);
424                }
425            } else {
426                logConfigurationError(Messages.get().getBundle().key(Messages.ERR_INVALID_USERAGENT_DEF_0), null);
427            }
428        }
429        setBrowserPattern(pattern);
430        setUserAgentsRegEx(userAgents);
431
432        Element paramsElement = (Element)(document.getRootElement().selectSingleNode(N_PARAMS));
433        if (paramsElement != null) {
434            List<?> params = paramsElement.selectNodes(N_PARAM);
435            for (Object paramObj : params) {
436                Element paramElement = (Element)paramObj;
437                String name = paramElement.attributeValue(A_NAME);
438                String value = paramElement.getText();
439                m_parameters.put(name, value);
440            }
441        }
442    }
443
444    /**
445     * Logs configuration errors and invalidates the current configuration.<p>
446     *
447     * @param message the message specifying the configuration error
448     * @param t the Throwable object or null
449     */
450    private void logConfigurationError(String message, Throwable t) {
451
452        setValidConfiguration(false);
453        if (LOG.isErrorEnabled()) {
454            if (t == null) {
455                LOG.error(Messages.get().getBundle().key(Messages.LOG_EDITOR_CONFIG_ERROR_1, message));
456            } else {
457                LOG.error(Messages.get().getBundle().key(Messages.LOG_EDITOR_CONFIG_ERROR_1, message), t);
458            }
459        }
460    }
461
462    /**
463     * Sets the list of compiled browser patterns.<p>
464     *
465     * @param pattern the list of compiled browser patterns
466     */
467    private void setBrowserPattern(List<Pattern> pattern) {
468
469        if ((pattern == null) || (pattern.size() == 0)) {
470            setValidConfiguration(false);
471            LOG.error(Messages.get().getBundle().key(Messages.LOG_EDITOR_CONFIG_NO_PATTERN_0));
472        }
473        m_browserPattern = pattern;
474    }
475
476    /**
477     * Sets the editor label key used for the localized nice name.<p>
478     *
479     * @param label the editor label key used for the localized nice name
480     */
481    private void setEditorLabel(String label) {
482
483        if (CmsStringUtil.isEmptyOrWhitespaceOnly(label)) {
484            setValidConfiguration(false);
485            LOG.error(Messages.get().getBundle().key(Messages.LOG_EDITOR_CONFIG_NO_LABEL_0));
486        }
487        m_editorLabel = label;
488    }
489
490    /**
491     * Sets the editor workplace URI.<p>
492     * @param uri the editor workplace URI
493     */
494    private void setEditorUri(String uri) {
495
496        if (CmsStringUtil.isEmptyOrWhitespaceOnly(uri)) {
497            setValidConfiguration(false);
498            LOG.error(Messages.get().getBundle().key(Messages.LOG_EDITOR_CONFIG_NO_URI_0));
499        }
500        m_editorUri = uri;
501    }
502
503    /**
504     * Sets the valid resource types of the editor.<p>
505     *
506     * @param types the valid resource types of the editor
507     */
508    private void setResourceTypes(Map<String, String[]> types) {
509
510        if ((types == null) || (types.size() == 0)) {
511            setValidConfiguration(false);
512            LOG.error(Messages.get().getBundle().key(Messages.LOG_NO_RESOURCE_TYPES_0));
513        }
514        m_resTypes = types;
515    }
516
517    /**
518     * Sets the valid user agents regular expressions of the editor.<p>
519     *
520     * @param agents the valid user agents regular expressions of the editor
521     */
522    private void setUserAgentsRegEx(List<String> agents) {
523
524        if ((agents == null) || (agents.size() == 0)) {
525            setValidConfiguration(false);
526            LOG.error(Messages.get().getBundle().key(Messages.LOG_NO_USER_AGENTS_0));
527        }
528        m_userAgentsRegEx = agents;
529    }
530
531    /**
532     * Sets if the current configuration is valid.<p>
533     *
534     * @param isValid true if no configuration errors were found, otherwise false
535     */
536    private void setValidConfiguration(boolean isValid) {
537
538        m_validConfiguration = isValid;
539    }
540
541    /**
542     * Sets the widget editor class for rich text editing.<p>
543     *
544     * @param widgetEditor the widget editor class for rich text editing
545     */
546    private void setWidgetEditor(String widgetEditor) {
547
548        m_widgetEditor = widgetEditor;
549    }
550
551}