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, 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.ade.configuration.plugins;
029
030import org.opencms.ade.configuration.CmsADEConfigData;
031import org.opencms.ade.containerpage.CmsDetailOnlyContainerUtil;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.jsp.CmsJspTagContainer;
036import org.opencms.jsp.Messages;
037import org.opencms.jsp.util.CmsJspStandardContextBean;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.relations.CmsLinkInfo;
042import org.opencms.xml.containerpage.CmsContainerBean;
043import org.opencms.xml.containerpage.CmsContainerElementBean;
044import org.opencms.xml.containerpage.CmsContainerPageBean;
045import org.opencms.xml.containerpage.CmsFormatterConfiguration;
046import org.opencms.xml.containerpage.I_CmsFormatterBean;
047
048import java.util.ArrayList;
049import java.util.Collections;
050import java.util.HashSet;
051import java.util.List;
052import java.util.Set;
053
054import javax.servlet.http.HttpServletRequest;
055
056import org.apache.commons.logging.Log;
057
058import com.google.common.collect.ArrayListMultimap;
059import com.google.common.collect.Multimap;
060
061/**
062 * Helper class for finding the list of active template plugins for the current page.
063 */
064public class CmsTemplatePluginFinder {
065
066    /** Log instance for this class. */
067    private static final Log LOG = CmsLog.getLog(CmsTemplatePluginFinder.class);
068
069    /** Current standard context bean. */
070    private CmsJspStandardContextBean m_standardContextBean;
071
072    /**
073     * Creates a new instance.
074     *
075     * @param standardContextBean the current standard context bean
076     */
077    public CmsTemplatePluginFinder(CmsJspStandardContextBean standardContextBean) {
078
079        m_standardContextBean = standardContextBean;
080    }
081
082    /**
083     * Gets the active plugins from site plugins only.
084     *
085     * @param config the sitemap configuration for which to get the plugins
086     *
087     * @return the multimap of active plugins by group
088     */
089    public static Multimap<String, CmsTemplatePlugin> getActiveTemplatePluginsFromSitePlugins(CmsADEConfigData config) {
090
091        List<CmsTemplatePlugin> plugins = new ArrayList<>();
092        for (CmsSitePlugin sitePlugin : config.getSitePlugins()) {
093            plugins.addAll(sitePlugin.getPlugins());
094        }
095        return getActivePlugins(plugins);
096    }
097
098    /**
099     * Collects the referenced plugins from the current page (from container elements, detail elements, etc.).
100     *
101     * @param standardContext the standard context bean
102     * @return the list of plugins (unsorted, with duplicates)
103     *
104     * @throws CmsException if something goes wrong
105     */
106    private static List<CmsTemplatePlugin> collectPluginsForCurrentPage(CmsJspStandardContextBean standardContext)
107    throws CmsException {
108
109        List<CmsTemplatePlugin> plugins = new ArrayList<>();
110        CmsObject cms = standardContext.getVfs().getCmsObject();
111        HttpServletRequest req = (HttpServletRequest)(standardContext.getRequest());
112        standardContext.initPage();
113
114        CmsContainerPageBean containerPage = standardContext.getPage();
115        CmsADEConfigData config = OpenCms.getADEManager().lookupConfigurationWithCache(
116            cms,
117            cms.getRequestContext().getRootUri());
118        if ((containerPage != null) && (containerPage.getElements() != null)) {
119            List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>(containerPage.getContainers().values());
120            // add detail only containers if available
121            if (standardContext.isDetailRequest()) {
122                CmsContainerPageBean detailOnly = CmsDetailOnlyContainerUtil.getDetailOnlyPage(
123                    cms,
124                    req,
125                    cms.getRequestContext().getRootUri());
126                if (detailOnly != null) {
127                    containers.addAll(detailOnly.getContainers().values());
128                }
129            }
130            for (CmsContainerBean container : containers) {
131                for (CmsContainerElementBean element : container.getElements()) {
132                    try {
133                        element.initResource(cms);
134                        if (!standardContext.getIsOnlineProject()
135                            || element.getResource().isReleasedAndNotExpired(
136                                cms.getRequestContext().getRequestTime())) {
137                            if (element.isGroupContainer(cms) || element.isInheritedContainer(cms)) {
138                                List<CmsContainerElementBean> subElements;
139                                if (element.isGroupContainer(cms)) {
140                                    subElements = CmsJspTagContainer.getGroupContainerElements(
141                                        cms,
142                                        element,
143                                        req,
144                                        container.getType());
145                                } else {
146                                    subElements = CmsJspTagContainer.getInheritedContainerElements(cms, element);
147                                }
148                                for (CmsContainerElementBean subElement : subElements) {
149                                    subElement.initResource(cms);
150                                    if (!standardContext.getIsOnlineProject()
151                                        || subElement.getResource().isReleasedAndNotExpired(
152                                            cms.getRequestContext().getRequestTime())) {
153                                        I_CmsFormatterBean formatter = getFormatterBeanForElement(
154                                            cms,
155                                            config,
156                                            subElement,
157                                            container);
158                                        if (formatter != null) {
159                                            plugins.addAll(formatter.getTemplatePlugins());
160                                        }
161                                    }
162                                }
163                            } else {
164                                I_CmsFormatterBean formatter = getFormatterBeanForElement(
165                                    cms,
166                                    config,
167                                    element,
168                                    container);
169                                if (formatter != null) {
170                                    plugins.addAll(formatter.getTemplatePlugins());
171                                }
172                            }
173                        }
174                    } catch (CmsException e) {
175                        LOG.error(
176                            Messages.get().getBundle().key(
177                                Messages.ERR_READING_REQUIRED_RESOURCE_1,
178                                element.getSitePath()),
179                            e);
180                    }
181                }
182            }
183        }
184        if (standardContext.getDetailContentId() != null) {
185            try {
186                CmsResource detailContent = cms.readResource(
187                    standardContext.getDetailContentId(),
188                    CmsResourceFilter.ignoreExpirationOffline(cms));
189                CmsFormatterConfiguration detailContentFormatters = config.getFormatters(cms, detailContent);
190                for (I_CmsFormatterBean formatter : detailContentFormatters.getDetailFormatters()) {
191                    plugins.addAll(formatter.getTemplatePlugins());
192                }
193            } catch (CmsException e) {
194                LOG.error(
195                    Messages.get().getBundle().key(
196                        Messages.ERR_READING_REQUIRED_RESOURCE_1,
197                        standardContext.getDetailContentId()),
198                    e);
199            }
200        }
201        for (CmsSitePlugin sitePlugin : config.getSitePlugins()) {
202            plugins.addAll(sitePlugin.getPlugins());
203        }
204        return plugins;
205    }
206
207    /**
208     * Gets the active plugins by group as a multimap, with each group sorted by descending order.
209     *
210     * @param plugins unsorted list of plugins, possibly with duplicates
211     * @return multimap of plugins by group
212     */
213    private static Multimap<String, CmsTemplatePlugin> getActivePlugins(List<CmsTemplatePlugin> plugins) {
214
215        Multimap<String, CmsTemplatePlugin> pluginsByGroup = ArrayListMultimap.create();
216        for (CmsTemplatePlugin plugin : plugins) {
217            pluginsByGroup.put(plugin.getGroup(), plugin);
218        }
219        Multimap<String, CmsTemplatePlugin> result = ArrayListMultimap.create();
220        for (String group : pluginsByGroup.keySet()) {
221            List<CmsTemplatePlugin> active = sortAndDeduplicatePlugins(
222                (List<CmsTemplatePlugin>)(pluginsByGroup.get(group)));
223            result.putAll(group, active);
224        }
225        return result;
226    }
227
228    /**
229     * Returns the formatter configuration for the given element, will return <code>null</code> for schema formatters.<p>
230     *
231     * @param cms the current CMS context
232     * @param config the current sitemap configuration
233     * @param element the element bean
234     * @param container the container bean
235     *
236     * @return the formatter configuration bean
237     */
238    private static I_CmsFormatterBean getFormatterBeanForElement(
239        CmsObject cms,
240        CmsADEConfigData config,
241        CmsContainerElementBean element,
242        CmsContainerBean container) {
243
244        int containerWidth = -1;
245        if (container.getWidth() == null) {
246            // the container width has not been set yet
247            containerWidth = CmsFormatterConfiguration.MATCH_ALL_CONTAINER_WIDTH;
248        } else {
249            try {
250                containerWidth = Integer.parseInt(container.getWidth());
251            } catch (NumberFormatException e) {
252                // do nothing, set width to -1
253            }
254        }
255        I_CmsFormatterBean result = CmsJspTagContainer.getFormatterConfigurationForElement(
256            cms,
257            element,
258            config,
259            container.getName(),
260            container.getType(),
261            containerWidth);
262        return result;
263    }
264
265    /**
266     * Sorts and de-duplicates a list of plugins.
267     *
268     * <p>This returns a new list and does not modify the input list.
269     *
270     * @param plugins the list of plugins
271     * @return the sorted and de-duplicated plugins
272     */
273    private static List<CmsTemplatePlugin> sortAndDeduplicatePlugins(List<CmsTemplatePlugin> plugins) {
274
275        List<CmsTemplatePlugin> result = new ArrayList<>();
276        Set<CmsLinkInfo> seenTargets = new HashSet<>();
277        // sort in descending order
278        Collections.sort(plugins, (p1, p2) -> Integer.compare(p2.getOrder(), p1.getOrder()));
279        for (CmsTemplatePlugin plugin : plugins) {
280            try {
281                // only add the first occurrence of a given target
282                CmsLinkInfo target = plugin.getTarget();
283                if (!seenTargets.contains(target)) {
284                    seenTargets.add(target);
285                    result.add(plugin);
286                }
287            } catch (Exception e) {
288                LOG.error(e.getLocalizedMessage(), e);
289            }
290        }
291        return result;
292    }
293
294    /**
295     * Gets the multimap of plugins for the current page by group, with each group sorted and de-duplicated.
296     *
297     * @return the multimap of plugins
298     */
299    public Multimap<String, CmsTemplatePlugin> getTemplatePlugins() {
300
301        try {
302            return getActivePlugins(collectPluginsForCurrentPage(m_standardContextBean));
303        } catch (Exception e) {
304            LOG.error(e.getLocalizedMessage(), e);
305            return ArrayListMultimap.create();
306        }
307    }
308
309}