org.opencms.xml.containerpage.CmsFormatterConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.xml.containerpage.CmsFormatterConfiguration.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.xml.containerpage;

import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.file.types.CmsResourceTypeJsp;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsRole;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Represents a formatter configuration.<p>
 *
 * A formatter configuration can be either defined in the XML schema XSD of a XML content,
 * or in a special sitemap configuration file.<p>
 *
 * @since 8.0.0
 */
public final class CmsFormatterConfiguration {

    /**
     * This class is used to sort lists of formatter beans in order of importance.<p>
     */
    public static class FormatterComparator implements Comparator<I_CmsFormatterBean> {

        /**
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare(I_CmsFormatterBean first, I_CmsFormatterBean second) {

            return ComparisonChain.start().compare(second.getRank(), first.getRank())
                    .compare(second.isTypeFormatter() ? 1 : 0, first.isTypeFormatter() ? 1 : 0)
                    .compare(second.getMinWidth(), first.getMinWidth()).result();
        }
    }

    /**
     * Predicate which checks whether the given formatter is a detail formatter.<p>
     */
    public static class IsDetail implements Predicate<I_CmsFormatterBean> {

        /**
         * @see com.google.common.base.Predicate#apply(java.lang.Object)
         */
        public boolean apply(I_CmsFormatterBean formatter) {

            return formatter.isDetailFormatter();
        }
    }

    /**
     * Predicate which checks whether the given formatter is a display formatter.<p>
     */
    public static class IsDisplay implements Predicate<I_CmsFormatterBean> {

        /**
         * @see com.google.common.base.Predicate#apply(java.lang.Object)
         */
        public boolean apply(I_CmsFormatterBean formatter) {

            return formatter.isDisplayFormatter();
        }
    }

    /**
     * Predicate to check whether the formatter is from a schema.<p>
     */
    public static class IsSchemaFormatter implements Predicate<I_CmsFormatterBean> {

        /**
         * @see com.google.common.base.Predicate#apply(java.lang.Object)
         */
        public boolean apply(I_CmsFormatterBean formatter) {

            return !formatter.isFromFormatterConfigFile();

        }
    }

    /**
     * Predicate which checks whether a formatter matches the given container type or width.<p>
     */
    private class MatchesTypeOrWidth implements Predicate<I_CmsFormatterBean> {

        /** If nested containers are allowed. */
        private boolean m_allowNested;

        /** The set of container types to match. */
        private Set<String> m_types = Sets.newHashSet();

        /** The container width. */
        private int m_width;

        /**
         * Creates a new matcher instance.<p>
         *
         * @param type the container type
         * @param width the container width
         * @param allowNested if nested containers are allowed
         */
        public MatchesTypeOrWidth(String type, int width, boolean allowNested) {

            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(type)) {
                // split with comma and optionally spaces to the left/right of the comma as separator
                m_types.addAll(Arrays.asList(type.trim().split(" *, *")));
            }
            m_width = width;
            m_allowNested = allowNested;
        }

        /**
         * @see com.google.common.base.Predicate#apply(java.lang.Object)
         */
        public boolean apply(I_CmsFormatterBean formatter) {

            return matchFormatter(formatter, m_types, m_width, m_allowNested);
        }
    }

    /** The empty formatter configuration. */
    public static final CmsFormatterConfiguration EMPTY_CONFIGURATION = new CmsFormatterConfiguration(null, null);

    /** The log instance for this class. */
    public static final Log LOG = CmsLog.getLog(CmsFormatterConfiguration.class);

    /** The container width to match all width configured formatters. */
    public static final int MATCH_ALL_CONTAINER_WIDTH = -2;

    /** CmsObject used to read the JSP resources configured in the XSD schema. */
    private static CmsObject m_adminCms;

    /** All formatters that have been added to this configuration. */
    private List<I_CmsFormatterBean> m_allFormatters;

    /** The available display formatters. */
    private List<I_CmsFormatterBean> m_displayFormatters;

    /** Cache for the searchContent option. */
    private Map<CmsUUID, Boolean> m_searchContent = Maps.newHashMap();

    /**
     * Creates a new formatter configuration based on the given list of formatters.<p>
     *
     * @param cms the current users OpenCms context
     * @param formatters the list of configured formatters
     */
    private CmsFormatterConfiguration(CmsObject cms, List<I_CmsFormatterBean> formatters) {

        if (formatters == null) {
            // this is needed for the empty configuration
            m_allFormatters = Collections.emptyList();
        } else {
            m_allFormatters = new ArrayList<I_CmsFormatterBean>(formatters);
        }
        init(cms, m_adminCms);
    }

    /**
     * Returns the formatter configuration for the current project based on the given list of formatters.<p>
     *
     * @param cms the current users OpenCms context, required to know which project to read the JSP from
     * @param formatters the list of configured formatters
     *
     * @return the formatter configuration for the current project based on the given list of formatters
     */
    public static CmsFormatterConfiguration create(CmsObject cms, List<I_CmsFormatterBean> formatters) {

        if ((formatters != null) && (formatters.size() > 0) && (cms != null)) {
            return new CmsFormatterConfiguration(cms, formatters);
        } else {
            return EMPTY_CONFIGURATION;
        }
    }

    /**
     * Initialize the formatter configuration.<p>
     *
     * @param cms an initialized admin OpenCms user context
     *
     * @throws CmsException in case the initialization fails
     */
    public static void initialize(CmsObject cms) throws CmsException {

        OpenCms.getRoleManager().checkRole(cms, CmsRole.ADMINISTRATOR);
        try {
            // store the Admin cms to index Cms resources
            m_adminCms = OpenCms.initCmsObject(cms);
            m_adminCms.getRequestContext().setSiteRoot("");
        } catch (CmsException e) {
            // this should never happen
        }
    }

    /**
     * Checks whether the given formatter bean matches the container types, width and nested flag.<p>
     *
     * @param formatter the formatter bean
     * @param types the container types
     * @param width the container width
     * @param allowNested whether nested containers are allowed
     *
     * @return <code>true</code> in case the formatter matches
     */
    public static boolean matchFormatter(I_CmsFormatterBean formatter, Set<String> types, int width,
            boolean allowNested) {

        if (!allowNested && formatter.hasNestedContainers()) {
            return false;
        }
        if (formatter.isMatchAll()) {
            return true;
        }
        if (formatter.isTypeFormatter()) {
            return !Sets.intersection(types, formatter.getContainerTypes()).isEmpty();
        } else {
            return (width == MATCH_ALL_CONTAINER_WIDTH)
                    || ((formatter.getMinWidth() <= width) && (width <= formatter.getMaxWidth()));
        }
    }

    /**
     * Gets a list of all defined formatters.<p>
     *
     * @return the list of all formatters
     */
    public List<I_CmsFormatterBean> getAllFormatters() {

        return new ArrayList<I_CmsFormatterBean>(m_allFormatters);
    }

    /**
     * Gets the formatters which are available for the given container type and width.<p>
     *
     * @param containerTypes the container types (comma separated)
     * @param containerWidth the container width
     * @param allowNested if nested containers are allowed
     *
     * @return the list of available formatters
     */
    public List<I_CmsFormatterBean> getAllMatchingFormatters(String containerTypes, int containerWidth,
            boolean allowNested) {

        return new ArrayList<I_CmsFormatterBean>(Collections2.filter(m_allFormatters,
                new MatchesTypeOrWidth(containerTypes, containerWidth, allowNested)));

    }

    /**
     * Selects the best matching formatter for the provided type and width from this configuration.<p>
     *
     * This method first tries to find the formatter for the provided container type.
     * If this fails, it returns the width based formatter that matched the container width.<p>
     *
     * @param containerTypes the container types (comma separated)
     * @param containerWidth the container width
     * @param allowNested if nested containers are allowed
     *
     * @return the matching formatter, or <code>null</code> if none was found
     */
    public I_CmsFormatterBean getDefaultFormatter(final String containerTypes, final int containerWidth,
            final boolean allowNested) {

        Optional<I_CmsFormatterBean> result = Iterables.tryFind(m_allFormatters,
                new MatchesTypeOrWidth(containerTypes, containerWidth, allowNested));
        return result.orNull();
    }

    /**
     * Selects the best matching schema formatter for the provided type and width from this configuration.<p>
     *
     * @param containerTypes the container types (comma separated)
     * @param containerWidth the container width
     *
     * @return the matching formatter, or <code>null</code> if none was found
     */
    public I_CmsFormatterBean getDefaultSchemaFormatter(final String containerTypes, final int containerWidth) {

        Optional<I_CmsFormatterBean> result = Iterables.tryFind(m_allFormatters, Predicates
                .and(new IsSchemaFormatter(), new MatchesTypeOrWidth(containerTypes, containerWidth, false)));
        return result.orNull();
    }

    /**
     * Gets the detail formatter to use for the given type and container width.<p>
     *
     * @param types the container types (comma separated)
     * @param containerWidth the container width
     *
     * @return the detail formatter to use
     */
    public I_CmsFormatterBean getDetailFormatter(String types, int containerWidth) {

        // detail formatters must still match the type or width
        Predicate<I_CmsFormatterBean> checkValidDetailFormatter = Predicates
                .and(new MatchesTypeOrWidth(types, containerWidth, true), new IsDetail());
        Optional<I_CmsFormatterBean> result = Iterables.tryFind(m_allFormatters, checkValidDetailFormatter);
        return result.orNull();
    }

    /**
     * Gets all detail formatters.<p>
     *
     * @return the detail formatters
     */
    public Collection<I_CmsFormatterBean> getDetailFormatters() {

        return Collections
                .<I_CmsFormatterBean>unmodifiableCollection(Collections2.filter(m_allFormatters, new IsDetail()));
    }

    /**
     * Returns the display formatter for this type.<p>
     *
     * @return the display formatter
     */
    public I_CmsFormatterBean getDisplayFormatter() {

        if (!getDisplayFormatters().isEmpty()) {
            return getDisplayFormatters().get(0);
        }
        return null;
    }

    /**
     * Returns the available display formatters.<p>
     *
     * @return the display formatters
     */
    public List<I_CmsFormatterBean> getDisplayFormatters() {

        if (m_displayFormatters == null) {
            List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>(
                    Collections2.filter(m_allFormatters, new IsDisplay()));
            if (formatters.size() > 1) {
                Collections.sort(formatters, new Comparator<I_CmsFormatterBean>() {

                    public int compare(I_CmsFormatterBean o1, I_CmsFormatterBean o2) {

                        return o1.getRank() == o2.getRank() ? 0 : (o1.getRank() < o2.getRank() ? -1 : 1);
                    }
                });
            }
            m_displayFormatters = Collections.unmodifiableList(formatters);
        }
        return m_displayFormatters;
    }

    /**
     * Returns the formatters available for selection for the given container type and width.<p>
     *
     * @param containerTypes the container types (comma separated)
     * @param containerWidth the container width
     * @param allowNested if nested containers are allowed
     *
     * @return the list of available formatters
     */
    public Map<String, I_CmsFormatterBean> getFormatterSelection(String containerTypes, int containerWidth,
            boolean allowNested) {

        Map<String, I_CmsFormatterBean> result = new LinkedHashMap<String, I_CmsFormatterBean>();
        for (I_CmsFormatterBean formatter : Collections2.filter(m_allFormatters,
                new MatchesTypeOrWidth(containerTypes, containerWidth, allowNested))) {
            if (formatter.isFromFormatterConfigFile()) {
                result.put(formatter.getId(), formatter);
            } else {
                result.put(CmsFormatterConfig.SCHEMA_FORMATTER_ID + formatter.getJspStructureId().toString(),
                        formatter);
            }
        }
        return result;
    }

    /**
     * Returns the formatter from this configuration that is to be used for the preview in the ADE gallery GUI,
     * or <code>null</code> if there is no preview formatter configured.<p>
     *
     * @return the formatter from this configuration that is to be used for the preview in the ADE gallery GUI,
     * or <code>null</code> if there is no preview formatter configured
     */
    public I_CmsFormatterBean getPreviewFormatter() {

        Optional<I_CmsFormatterBean> result;
        result = Iterables.tryFind(m_allFormatters, new Predicate<I_CmsFormatterBean>() {

            public boolean apply(I_CmsFormatterBean formatter) {

                return formatter.isPreviewFormatter();
            }
        });
        if (!result.isPresent()) {
            result = Iterables.tryFind(m_allFormatters, new Predicate<I_CmsFormatterBean>() {

                public boolean apply(I_CmsFormatterBean formatter) {

                    if (formatter.isTypeFormatter()) {
                        return formatter.getContainerTypes().contains(CmsFormatterBean.PREVIEW_TYPE);
                    } else {
                        return (formatter.getMinWidth() <= CmsFormatterBean.PREVIEW_WIDTH)
                                && (CmsFormatterBean.PREVIEW_WIDTH <= formatter.getMaxWidth());
                    }
                }
            });
        }
        if (!result.isPresent()) {
            result = Iterables.tryFind(m_allFormatters, new Predicate<I_CmsFormatterBean>() {

                public boolean apply(I_CmsFormatterBean formatter) {

                    return !formatter.isTypeFormatter()
                            && (formatter.getMaxWidth() >= CmsFormatterBean.PREVIEW_WIDTH);

                }
            });
        }
        if (!result.isPresent() && !m_allFormatters.isEmpty()) {
            result = Optional.fromNullable(m_allFormatters.iterator().next());
        }
        return result.orNull();
    }

    /**
     * Returns the provided <code>true</code> in case this configuration has a formatter
     * for the given type / width parameters.<p>
     *
     * @param containerTypes the container types (comma separated)
     * @param containerWidth the container width
     * @param allowNested if nested containers are allowed
     *
     * @return the provided <code>true</code> in case this configuration has a formatter
     *      for the given type / width parameters.
     */
    public boolean hasFormatter(String containerTypes, int containerWidth, boolean allowNested) {

        return getDefaultFormatter(containerTypes, containerWidth, allowNested) != null;
    }

    /**
     * Returns <code>true</code> in case there is at least one usable formatter configured in this configuration.<p>
     *
     * @return <code>true</code> in case there is at least one usable formatter configured in this configuration
     */
    public boolean hasFormatters() {

        return !m_allFormatters.isEmpty();
    }

    /**
     * Returns <code>true</code> in case this configuration contains a formatter with the
     * provided structure id that has been configured for including the formatted content in the online search.<p>
     *
     * @param formatterStructureId the formatter structure id
     *
     * @return <code>true</code> in case this configuration contains a formatter with the
     * provided structure id that has been configured for including the formatted content in the online search
     */
    public boolean isSearchContent(CmsUUID formatterStructureId) {

        if (EMPTY_CONFIGURATION == this) {
            // don't search if this is just the empty configuration
            return false;
        }
        // lookup the cache
        Boolean result = m_searchContent.get(formatterStructureId);
        if (result == null) {
            // result so far unknown
            for (I_CmsFormatterBean formatter : m_allFormatters) {
                if (formatter.getJspStructureId().equals(formatterStructureId)) {
                    // found the match
                    result = Boolean.valueOf(formatter.isSearchContent());
                    // first match rules
                    break;
                }
            }
            if (result == null) {
                // no match found, in this case dont search the content
                result = Boolean.FALSE;
            }
            // store result in the cache
            m_searchContent.put(formatterStructureId, result);
        }

        return result.booleanValue();
    }

    /**
     * Initializes all formatters of this configuration.<p>
     *
     * It is also checked if the configured JSP root path exists, if not the formatter is removed
     * as it is unusable.<p>
     *
     * @param userCms the current users OpenCms context, used for selecting the right project
     * @param adminCms the Admin user context to use for reading the JSP resources
     */
    private void init(CmsObject userCms, CmsObject adminCms) {

        List<I_CmsFormatterBean> filteredFormatters = new ArrayList<I_CmsFormatterBean>();
        for (I_CmsFormatterBean formatter : m_allFormatters) {

            if (formatter.getJspStructureId() == null) {
                // a formatter may have been re-used so the structure id is already available
                CmsResource res = null;
                // first we make sure that the JSP exists at all (and also we read the UUID that way)
                try {
                    // first get a cms copy so we can mess up the context without modifying the original
                    CmsObject cmsCopy = OpenCms.initCmsObject(adminCms);
                    cmsCopy.getRequestContext().setCurrentProject(userCms.getRequestContext().getCurrentProject());
                    // switch to the root site
                    cmsCopy.getRequestContext().setSiteRoot("");
                    // now read the JSP
                    res = cmsCopy.readResource(formatter.getJspRootPath());
                } catch (CmsException e) {
                    //if this happens the result is null and we write a LOG error
                }
                if ((res == null) || !CmsResourceTypeJsp.isJsp(res)) {
                    // the formatter must exist and it must be a JSP
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_FORMATTER_JSP_DONT_EXIST_1,
                            formatter.getJspRootPath()));
                } else {
                    formatter.setJspStructureId(res.getStructureId());
                    // res may still be null in case of failure
                }
            }

            if (formatter.getJspStructureId() != null) {
                filteredFormatters.add(formatter);
            } else {
                LOG.warn("Invalid formatter: " + formatter.getJspRootPath());
            }
        }
        Collections.sort(filteredFormatters, new FormatterComparator());
        m_allFormatters = Collections.unmodifiableList(filteredFormatters);
    }

}