001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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, 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.site.xmlsitemap;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.file.types.I_CmsResourceType;
034import org.opencms.jsp.CmsJspActionElement;
035import org.opencms.main.CmsException;
036import org.opencms.main.CmsLog;
037import org.opencms.main.OpenCms;
038import org.opencms.util.CmsFileUtil;
039import org.opencms.util.CmsStringUtil;
040
041import java.lang.reflect.Constructor;
042import java.util.List;
043
044import org.apache.commons.logging.Log;
045
046import jakarta.servlet.http.HttpServletRequest;
047import jakarta.servlet.http.HttpServletResponse;
048import jakarta.servlet.jsp.PageContext;
049
050/**
051 * Action element class for displaying the XML sitemap from a JSP.<p>
052 */
053public class CmsXmlSitemapActionElement extends CmsJspActionElement {
054
055    /** The logger instance for this class. */
056    private static final Log LOG = CmsLog.getLog(CmsXmlSitemapActionElement.class);
057
058    /** Runtime property name for the default sitemap generator class. */
059    private static final String PARAM_DEFAULT_SITEMAP_GENERATOR = "sitemap.generator";
060
061    /** The configuration bean. */
062    protected CmsXmlSeoConfiguration m_configuration;
063
064    /**
065     * Constructor, with parameters.
066     *
067     * @param pageContext the JSP page context object
068     * @param request the JSP request
069     * @param response the JSP response
070     */
071    public CmsXmlSitemapActionElement(
072        PageContext pageContext,
073        HttpServletRequest request,
074        HttpServletResponse response) {
075
076        super(pageContext, request, response);
077    }
078
079    /**
080     * Creates an XML sitemap generator instance given a class name and the root path for the sitemap.<p>
081     *
082     * @param className the class name of the sitemap generator (may be null for the default
083     * @param folderRootPath the root path of the start folder for the sitemap
084     *
085     * @return the sitemap generator instance
086     *
087     * @throws CmsException if something goes wrong
088     */
089    public static CmsXmlSitemapGenerator createSitemapGenerator(String className, String folderRootPath)
090    throws CmsException {
091
092        if (CmsStringUtil.isEmptyOrWhitespaceOnly(className)) {
093            className = (String)(OpenCms.getRuntimeProperty(PARAM_DEFAULT_SITEMAP_GENERATOR));
094        }
095        if (CmsStringUtil.isEmptyOrWhitespaceOnly(className)) {
096            className = CmsXmlSitemapGenerator.class.getName();
097        }
098        try {
099            Class<? extends CmsXmlSitemapGenerator> generatorClass = Class.forName(className).asSubclass(
100                CmsXmlSitemapGenerator.class);
101            Constructor<? extends CmsXmlSitemapGenerator> constructor = generatorClass.getConstructor(String.class);
102            CmsXmlSitemapGenerator generator = constructor.newInstance(folderRootPath);
103            return generator;
104        } catch (Exception e) {
105            LOG.error(
106                "Could not create configured sitemap generator " + className + ", using the default class instead",
107                e);
108            return new CmsXmlSitemapGenerator(folderRootPath);
109        }
110    }
111
112    /**
113     * Constructs an XML sitemap generator given an XML sitemap configuration file.<p>
114     *
115     * @param seoFileRes the sitemap XML file
116     * @param config the parsed configuration
117     *
118     * @return the sitemap generator, or null if the given configuration is not an XML sitemap configuration
119     *
120     * @throws CmsException if something goes wrong
121     */
122    public static CmsXmlSitemapGenerator prepareSitemapGenerator(CmsResource seoFileRes, CmsXmlSeoConfiguration config)
123    throws CmsException {
124
125        if (config.getMode().equals(CmsXmlSeoConfiguration.MODE_XML_SITEMAP)) {
126            String baseFolderRootPath = CmsFileUtil.removeTrailingSeparator(
127                CmsResource.getParentFolder(seoFileRes.getRootPath()));
128            CmsXmlSitemapGenerator xmlSitemapGenerator = createSitemapGenerator(
129                config.getSitemapGeneratorClassName(),
130                baseFolderRootPath);
131            xmlSitemapGenerator.setComputeContainerPageDates(config.shouldComputeContainerPageModificationDates());
132            CmsPathIncludeExcludeSet inexcludeSet = xmlSitemapGenerator.getIncludeExcludeSet();
133            for (String include : config.getIncludes()) {
134                inexcludeSet.addInclude(include);
135            }
136            for (String exclude : config.getExcludes()) {
137                inexcludeSet.addExclude(exclude);
138            }
139            xmlSitemapGenerator.setServerUrl(config.getServerUrl());
140            return xmlSitemapGenerator;
141        }
142        return null;
143    }
144
145    /**
146     * Displays either the generated sitemap.xml or the generated robots.txt, depending on the configuration.<p>
147     *
148     * @throws Exception if something goes wrong
149     */
150    public void run() throws Exception {
151
152        CmsObject cms = getCmsObject();
153        String seoFilePath = cms.getRequestContext().getUri();
154        CmsResource seoFile = cms.readResource(seoFilePath);
155        m_configuration = new CmsXmlSeoConfiguration();
156        m_configuration.load(cms, seoFile);
157        String mode = m_configuration.getMode();
158        if (mode.equals(CmsXmlSeoConfiguration.MODE_ROBOTS_TXT)) {
159            showRobotsTxt();
160        } else {
161            boolean updateCache = Boolean.parseBoolean(getRequest().getParameter("updateCache"));
162            String value = "";
163            if (updateCache && m_configuration.usesCache()) {
164                // update request, and caching is configured -> update the cache
165                CmsXmlSitemapGenerator generator = prepareSitemapGenerator(seoFile, m_configuration);
166                value = generator.renderSitemap();
167                CmsXmlSitemapCache.INSTANCE.put(seoFile.getRootPath(), value);
168            } else if (!updateCache && m_configuration.usesCache()) {
169                // normal request, and caching is configured -> look in the cache first, and if not found, calculate sitemap and store it in cache
170                String cachedValue = CmsXmlSitemapCache.INSTANCE.get(seoFile.getRootPath());
171                if (cachedValue != null) {
172                    value = cachedValue;
173                } else {
174                    CmsXmlSitemapGenerator generator = prepareSitemapGenerator(seoFile, m_configuration);
175                    value = generator.renderSitemap();
176                    CmsXmlSitemapCache.INSTANCE.put(seoFile.getRootPath(), value);
177                }
178            } else if (!updateCache && !m_configuration.usesCache()) {
179                // normal request, caching is not configured -> always generate a fresh sitemap
180                CmsXmlSitemapGenerator generator = prepareSitemapGenerator(seoFile, m_configuration);
181                value = generator.renderSitemap();
182            } else if (updateCache && !m_configuration.usesCache()) {
183                // update request with no caching configured -> ignore
184            }
185            getResponse().getWriter().print(value);
186        }
187
188    }
189
190    /**
191     * Renders the robots.txt data containing the sitemaps automatically.<p>
192     *
193     * @throws Exception if something goes wrong
194     */
195    private void showRobotsTxt() throws Exception {
196
197        CmsObject cms = getCmsObject();
198        StringBuffer buffer = new StringBuffer();
199        I_CmsResourceType seoFileType = OpenCms.getResourceManager().getResourceType(
200            CmsXmlSeoConfiguration.SEO_FILE_TYPE);
201        List<CmsResource> seoFiles = cms.readResources(
202            "/",
203            CmsResourceFilter.DEFAULT_FILES.addRequireVisible().addRequireType(seoFileType));
204        for (CmsResource seoFile : seoFiles) {
205            try {
206                if (seoFile.getName().contains("test")) {
207                    continue;
208                }
209                CmsXmlSeoConfiguration seoFileConfig = new CmsXmlSeoConfiguration();
210                seoFileConfig.load(cms, seoFile);
211                if (seoFileConfig.isXmlSitemapMode()) {
212                    buffer.append(
213                        "Sitemap: "
214                            + CmsXmlSitemapGenerator.replaceServerUri(
215                                OpenCms.getLinkManager().getOnlineLink(cms, cms.getSitePath(seoFile)),
216                                m_configuration.getServerUrl()));
217                    buffer.append("\n");
218                }
219            } catch (CmsException e) {
220                LOG.error("Error while generating robots.txt : " + e.getLocalizedMessage(), e);
221            }
222        }
223        buffer.append("\n");
224        buffer.append(m_configuration.getRobotsTxtText());
225        buffer.append("\n");
226        getResponse().getWriter().print(buffer.toString());
227    }
228
229}