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.staticexport;
029
030import org.opencms.configuration.CmsConfigurationException;
031
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.HashSet;
035import java.util.List;
036import java.util.Locale;
037import java.util.Set;
038import java.util.regex.Pattern;
039
040/**
041 * Configuration for the shared cache delivery mode.<p>
042 */
043public class CmsSharedCacheConfiguration {
044
045    /** Pattern for an exact MIME type or a top-level wildcard. */
046    private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(
047        "[a-z0-9!#$&^_.+-]+/(?:\\*|[a-z0-9!#$&^_.+-]+)");
048
049    /** If this configuration was explicitly configured. */
050    private boolean m_configured;
051
052    /** If shared cache delivery is enabled. */
053    private boolean m_enabled;
054
055    /** The configured cache policies. */
056    private List<CmsSharedCachePolicy> m_policies = new ArrayList<CmsSharedCachePolicy>();
057
058    /**
059     * Adds a cache policy.<p>
060     *
061     * @param policy the cache policy
062     */
063    public void addCachePolicy(CmsSharedCachePolicy policy) {
064
065        if (policy != null) {
066            m_policies.add(policy);
067            m_configured = true;
068        }
069    }
070
071    /**
072     * Returns the configured cache policies in configuration order.<p>
073     *
074     * @return the configured cache policies
075     */
076    public List<CmsSharedCachePolicy> getCachePolicies() {
077
078        return Collections.unmodifiableList(m_policies);
079    }
080
081    /**
082     * Returns the cache policy for a content type.<p>
083     *
084     * Exact content type policies take precedence over top-level wildcard policies. The default policy is returned
085     * if no content type policy matches.<p>
086     *
087     * @param contentType the content type
088     * @return the matching cache policy, or the default policy
089     */
090    public CmsSharedCachePolicy getCachePolicy(String contentType) {
091
092        String normalizedContentType = normalizeContentType(contentType);
093        CmsSharedCachePolicy wildcardPolicy = null;
094        if (normalizedContentType != null) {
095            int separator = normalizedContentType.indexOf('/');
096            String wildcard = separator < 0 ? null : normalizedContentType.substring(0, separator) + "/*";
097            for (CmsSharedCachePolicy policy : m_policies) {
098                if (normalizedContentType.equals(policy.getContentType())) {
099                    return policy;
100                }
101                if ((wildcard != null) && wildcard.equals(policy.getContentType())) {
102                    wildcardPolicy = policy;
103                }
104            }
105        }
106        return wildcardPolicy != null ? wildcardPolicy : getDefaultCachePolicy();
107    }
108
109    /**
110     * Returns the default cache policy.<p>
111     *
112     * @return the default cache policy, or {@code null} if none is configured
113     */
114    public CmsSharedCachePolicy getDefaultCachePolicy() {
115
116        for (CmsSharedCachePolicy policy : m_policies) {
117            if (policy.isDefault()) {
118                return policy;
119            }
120        }
121        return null;
122    }
123
124    /**
125     * Returns whether shared cache settings have been configured explicitly.<p>
126     *
127     * @return {@code true} if shared cache settings have been configured
128     */
129    public boolean isConfigured() {
130
131        return m_configured;
132    }
133
134    /**
135     * Returns whether shared cache delivery is enabled.<p>
136     *
137     * @return {@code true} if shared cache delivery is enabled
138     */
139    public boolean isEnabled() {
140
141        return m_enabled;
142    }
143
144    /**
145     * Sets whether shared cache delivery is enabled.<p>
146     *
147     * @param enabled if shared cache delivery is enabled
148     */
149    public void setEnabled(String enabled) {
150
151        m_enabled = Boolean.valueOf(enabled).booleanValue();
152        m_configured = true;
153    }
154
155    /**
156     * Validates the shared cache configuration.<p>
157     *
158     * @throws CmsConfigurationException if the configuration is invalid
159     */
160    public void validate() throws CmsConfigurationException {
161
162        if (!m_configured) {
163            return;
164        }
165        int defaultPolicyCount = 0;
166        Set<String> contentTypes = new HashSet<String>();
167        for (CmsSharedCachePolicy policy : m_policies) {
168            String policyName = policy.isDefault() ? "default" : policy.getContentType();
169            if (policy.isDefault()) {
170                defaultPolicyCount++;
171            } else {
172                if (!CONTENT_TYPE_PATTERN.matcher(policy.getContentType()).matches()) {
173                    throw new CmsConfigurationException(
174                        org.opencms.configuration.Messages.get().container(
175                            org.opencms.configuration.Messages.ERR_SHAREDCACHE_POLICY_CONTENTTYPE_1,
176                            policy.getContentType()));
177                }
178                if (!contentTypes.add(policy.getContentType())) {
179                    throw new CmsConfigurationException(
180                        org.opencms.configuration.Messages.get().container(
181                            org.opencms.configuration.Messages.ERR_SHAREDCACHE_POLICY_DUPLICATE_1,
182                            policy.getContentType()));
183                }
184            }
185            validateDuration("clientmaxage", policyName, policy.getClientMaxAge(), false);
186            validateDuration("sharedmaxage", policyName, policy.getSharedMaxAge(), false);
187            validateDuration("staleiferror", policyName, policy.getStaleIfError(), true);
188        }
189        if (defaultPolicyCount != 1) {
190            throw new CmsConfigurationException(
191                org.opencms.configuration.Messages.get().container(
192                    org.opencms.configuration.Messages.ERR_SHAREDCACHE_DEFAULT_POLICY_1,
193                    Integer.valueOf(defaultPolicyCount)));
194        }
195    }
196
197    /**
198     * Normalizes a response content type for matching.<p>
199     *
200     * @param contentType the content type
201     * @return the normalized content type
202     */
203    private String normalizeContentType(String contentType) {
204
205        if (contentType == null) {
206            return null;
207        }
208        int parameterSeparator = contentType.indexOf(';');
209        String result = parameterSeparator < 0 ? contentType : contentType.substring(0, parameterSeparator);
210        result = result.trim().toLowerCase(Locale.ROOT);
211        return result.isEmpty() ? null : result;
212    }
213
214    /**
215     * Validates a configured duration.<p>
216     *
217     * @param fieldName the XML field name
218     * @param policyName the policy name
219     * @param value the configured value
220     * @param optional whether the duration is optional
221     *
222     * @throws CmsConfigurationException if the duration is invalid
223     */
224    private void validateDuration(String fieldName, String policyName, long value, boolean optional)
225    throws CmsConfigurationException {
226
227        if ((value < 0) && !(optional && (value == CmsSharedCachePolicy.DURATION_UNSET))) {
228            throw new CmsConfigurationException(
229                org.opencms.configuration.Messages.get().container(
230                    org.opencms.configuration.Messages.ERR_SHAREDCACHE_POLICY_DURATION_2,
231                    fieldName,
232                    policyName));
233        }
234    }
235}