blob: fdbc46f3faddd020d8729cac3155882aa5a96fa9 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.ofbiz.product.category;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;
import javolution.util.FastList;
import javolution.util.FastMap;
import javolution.util.FastSet;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.Perl5Compiler;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.StringUtil;
import org.ofbiz.base.util.UtilURL;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.UtilXml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* SeoConfigUtil - SEO Configuration file utility.
*
*/
public class SeoConfigUtil {
private static final String module = SeoConfigUtil.class.getName();
private static Perl5Compiler perlCompiler = new Perl5Compiler();
private static boolean isInitialed = false;
private static boolean categoryUrlEnabled = true;
private static boolean categoryNameEnabled = false;
private static String categoryUrlSuffix = null;
public static final String DEFAULT_REGEXP = "^.*/.*$";
private static Pattern regexpIfMatch = null;
private static boolean useUrlRegexp = false;
private static boolean jSessionIdAnonEnabled = false;
private static boolean jSessionIdUserEnabled = false;
private static Map<String, String> seoReplacements = null;
private static Map<String, Pattern> seoPatterns = null;
private static Map<String, String> forwardReplacements = null;
private static Map<String, Integer> forwardResponseCodes = null;
private static Map<String, String> charFilters = null;
private static List<Pattern> userExceptionPatterns = null;
private static Set<String> allowedContextPaths = null;
private static Map<String, String> specialProductIds = null;
public static final String ELEMENT_REGEXPIFMATCH = "regexpifmatch";
public static final String ELEMENT_URL_CONFIG = "url-config";
public static final String ELEMENT_DESCRIPTION = "description";
public static final String ELEMENT_FORWARD = "forward";
public static final String ELEMENT_SEO = "seo";
public static final String ELEMENT_URLPATTERN = "url-pattern";
public static final String ELEMENT_REPLACEMENT = "replacement";
public static final String ELEMENT_RESPONSECODE = "responsecode";
public static final String ELEMENT_JSESSIONID = "jsessionid";
public static final String ELEMENT_ANONYMOUS = "anonymous";
public static final String ELEMENT_VALUE = "value";
public static final String ELEMENT_USER = "user";
public static final String ELEMENT_EXCEPTIONS = "exceptions";
public static final String ELEMENT_CHAR_FILTERS = "char-filters";
public static final String ELEMENT_CHAR_FILTER = "char-filter";
public static final String ELEMENT_CHARACTER_PATTERN = "character-pattern";
public static final String ELEMENT_CATEGORY_URL = "category-url";
public static final String ELEMENT_ALLOWED_CONTEXT_PATHS = "allowed-context-paths";
public static final String ELEMENT_CATEGORY_NAME = "category-name";
public static final String ELEMENT_CATEGORY_URL_SUFFIX = "category-url-suffix";
public static final String SEO_CONFIG_FILENAME = "SeoConfig.xml";
public static final int DEFAULT_RESPONSECODE = HttpServletResponse.SC_MOVED_PERMANENTLY;
public static final String DEFAULT_ANONYMOUS_VALUE = "disable";
public static final String DEFAULT_USER_VALUE = "disable";
public static final String DEFAULT_CATEGORY_URL_VALUE = "enable";
public static final String DEFAULT_CATEGORY_NAME_VALUE = "disable";
public static final String ALLOWED_CONTEXT_PATHS_SEPERATOR = ":";
/**
* Initialize url regular express configuration.
*
* @return result to indicate the status of initialization.
*/
public static void init() {
FileInputStream configFileIS = null;
String result = "success";
seoPatterns = new HashMap<String, Pattern>();
seoReplacements = new HashMap<String, String>();
forwardReplacements = new HashMap<String, String>();
forwardResponseCodes = new HashMap<String, Integer>();
userExceptionPatterns = FastList.newInstance();
specialProductIds = FastMap.newInstance();
charFilters = FastMap.newInstance();
try {
URL seoConfigFilename = UtilURL.fromResource(SEO_CONFIG_FILENAME);
Document configDoc = UtilXml.readXmlDocument(seoConfigFilename, false);
Element rootElement = configDoc.getDocumentElement();
String regexIfMatch = UtilXml.childElementValue(rootElement, ELEMENT_REGEXPIFMATCH, DEFAULT_REGEXP);
Debug.logInfo("Parsing " + regexIfMatch, module);
try {
regexpIfMatch = perlCompiler.compile(regexIfMatch, Perl5Compiler.READ_ONLY_MASK);
} catch (MalformedPatternException e1) {
Debug.logWarning(e1, "Error while parsing " + regexIfMatch, module);
}
// parse category-url element
try {
Element categoryUrlElement = UtilXml.firstChildElement(rootElement, ELEMENT_CATEGORY_URL);
Debug.logInfo("Parsing " + ELEMENT_CATEGORY_URL + " [" + (categoryUrlElement != null) + "]:", module);
if (categoryUrlElement != null) {
String enableCategoryUrlValue = UtilXml.childElementValue(categoryUrlElement, ELEMENT_VALUE, DEFAULT_CATEGORY_URL_VALUE);
if (DEFAULT_CATEGORY_URL_VALUE.equalsIgnoreCase(enableCategoryUrlValue)) {
categoryUrlEnabled = true;
} else {
categoryUrlEnabled = false;
}
if (categoryUrlEnabled) {
String allowedContextValue = UtilXml.childElementValue(categoryUrlElement, ELEMENT_ALLOWED_CONTEXT_PATHS, null);
allowedContextPaths = FastSet.newInstance();
if (UtilValidate.isNotEmpty(allowedContextValue)) {
List<String> allowedContextPathList = StringUtil.split(allowedContextValue, ALLOWED_CONTEXT_PATHS_SEPERATOR);
for (String path : allowedContextPathList) {
if (UtilValidate.isNotEmpty(path)) {
path = path.trim();
if (!allowedContextPaths.contains(path)) {
allowedContextPaths.add(path);
Debug.logInfo(" " + ELEMENT_ALLOWED_CONTEXT_PATHS + ": " + path, module);
}
}
}
}
String categoryNameValue = UtilXml.childElementValue(categoryUrlElement, ELEMENT_CATEGORY_NAME, DEFAULT_CATEGORY_NAME_VALUE);
if (DEFAULT_CATEGORY_NAME_VALUE.equalsIgnoreCase(categoryNameValue)) {
categoryNameEnabled = false;
} else {
categoryNameEnabled = true;
}
Debug.logInfo(" " + ELEMENT_CATEGORY_NAME + ": " + categoryNameEnabled, module);
categoryUrlSuffix = UtilXml.childElementValue(categoryUrlElement, ELEMENT_CATEGORY_URL_SUFFIX, null);
if (UtilValidate.isNotEmpty(categoryUrlSuffix)) {
categoryUrlSuffix = categoryUrlSuffix.trim();
if (categoryUrlSuffix.contains("/")) {
categoryUrlSuffix = null;
}
}
Debug.logInfo(" " + ELEMENT_CATEGORY_URL_SUFFIX + ": " + categoryUrlSuffix, module);
}
}
} catch (NullPointerException e) {
// no "category-url" element
Debug.logWarning("No category-url element found in " + seoConfigFilename.toString(), module);
}
// parse jsessionid element
try {
Element jSessionId = UtilXml.firstChildElement(rootElement, ELEMENT_JSESSIONID);
Debug.logInfo("Parsing " + ELEMENT_JSESSIONID + " [" + (jSessionId != null) + "]:", module);
if (jSessionId != null) {
Element anonymous = UtilXml.firstChildElement(jSessionId, ELEMENT_ANONYMOUS);
if (anonymous != null) {
String anonymousValue = UtilXml.childElementValue(anonymous, ELEMENT_VALUE, DEFAULT_ANONYMOUS_VALUE);
if (DEFAULT_ANONYMOUS_VALUE.equalsIgnoreCase(anonymousValue)) {
jSessionIdAnonEnabled = false;
} else {
jSessionIdAnonEnabled = true;
}
} else {
jSessionIdAnonEnabled = Boolean.valueOf(DEFAULT_ANONYMOUS_VALUE).booleanValue();
}
Debug.logInfo(" " + ELEMENT_ANONYMOUS + ": " + jSessionIdAnonEnabled, module);
Element user = UtilXml.firstChildElement(jSessionId, ELEMENT_USER);
if (user != null) {
String userValue = UtilXml.childElementValue(user, ELEMENT_VALUE, DEFAULT_USER_VALUE);
if (DEFAULT_USER_VALUE.equalsIgnoreCase(userValue)) {
jSessionIdUserEnabled = false;
} else {
jSessionIdUserEnabled = true;
}
Element exceptions = UtilXml.firstChildElement(user, ELEMENT_EXCEPTIONS);
if (exceptions != null) {
Debug.logInfo(" " + ELEMENT_EXCEPTIONS + ": ", module);
List<? extends Element> exceptionUrlPatterns = UtilXml.childElementList(exceptions, ELEMENT_URLPATTERN);
for (int i = 0; i < exceptionUrlPatterns.size(); i++) {
Element element = exceptionUrlPatterns.get(i);
String urlpattern = element.getTextContent();
if (UtilValidate.isNotEmpty(urlpattern)) {
try {
Pattern pattern = perlCompiler.compile(urlpattern, Perl5Compiler.READ_ONLY_MASK);
userExceptionPatterns.add(pattern);
Debug.logInfo(" " + ELEMENT_URLPATTERN + ": " + urlpattern, module);
} catch (MalformedPatternException e) {
Debug.logWarning("Can NOT parse " + urlpattern + " in element " + ELEMENT_URLPATTERN + " of " + ELEMENT_EXCEPTIONS + ". Error: " + e.getMessage(), module);
}
}
}
}
} else {
jSessionIdUserEnabled = Boolean.valueOf(DEFAULT_USER_VALUE).booleanValue();
}
Debug.logInfo(" " + ELEMENT_USER + ": " + jSessionIdUserEnabled, module);
}
} catch (NullPointerException e) {
Debug.logWarning("No jsessionid element found in " + seoConfigFilename.toString(), module);
}
// parse url-config elements
try {
NodeList configs = rootElement.getElementsByTagName(ELEMENT_URL_CONFIG);
Debug.logInfo("Parsing " + ELEMENT_URL_CONFIG, module);
for (int j = 0; j < configs.getLength(); j++) {
Element config = (Element) configs.item(j);
String urlpattern = UtilXml.childElementValue(config, ELEMENT_URLPATTERN, null);
if (UtilValidate.isEmpty(urlpattern)) {
continue;
}
Debug.logInfo(" " + ELEMENT_URLPATTERN + ": " + urlpattern, module);
Pattern pattern;
try {
pattern = perlCompiler.compile(urlpattern, Perl5Compiler.READ_ONLY_MASK);
seoPatterns.put(urlpattern, pattern);
} catch (MalformedPatternException e) {
Debug.logWarning("Error while creating parttern for seo url-pattern: " + urlpattern, module);
continue;
}
// construct seo patterns
Element seo = UtilXml.firstChildElement(config, ELEMENT_SEO);
if (UtilValidate.isNotEmpty(seo)) {
String replacement = UtilXml.childElementValue(seo, ELEMENT_REPLACEMENT, null);
if (UtilValidate.isNotEmpty(replacement)) {
seoReplacements.put(urlpattern, replacement);
Debug.logInfo(" " + ELEMENT_SEO + " " + ELEMENT_REPLACEMENT + ": " + replacement, module);
}
}
// construct forward patterns
Element forward = UtilXml.firstChildElement(config, ELEMENT_FORWARD);
if (UtilValidate.isNotEmpty(forward)) {
String replacement = UtilXml.childElementValue(forward, ELEMENT_REPLACEMENT, null);
String responseCode = UtilXml.childElementValue(forward,
ELEMENT_RESPONSECODE, String.valueOf(DEFAULT_RESPONSECODE));
if (UtilValidate.isNotEmpty(replacement)) {
forwardReplacements.put(urlpattern, replacement);
Debug.logInfo(" " + ELEMENT_FORWARD + " " + ELEMENT_REPLACEMENT + ": " + replacement, module);
if (UtilValidate.isNotEmpty(responseCode)) {
Integer responseCodeInt = DEFAULT_RESPONSECODE;
try {
responseCodeInt = Integer.valueOf(responseCode);
} catch (NumberFormatException nfe) {
Debug.logWarning(nfe, "Error while parsing response code number: " + responseCode, module);
}
forwardResponseCodes.put(urlpattern, responseCodeInt);
Debug.logInfo(" " + ELEMENT_FORWARD + " " + ELEMENT_RESPONSECODE + ": " + responseCodeInt, module);
}
}
}
}
} catch (NullPointerException e) {
// no "url-config" element
Debug.logWarning("No " + ELEMENT_URL_CONFIG + " element found in " + seoConfigFilename.toString(), module);
}
// parse char-filters elements
try {
NodeList nameFilterNodes = rootElement
.getElementsByTagName(ELEMENT_CHAR_FILTER);
Debug.logInfo("Parsing " + ELEMENT_CHAR_FILTER + ": ", module);
for (int i = 0; i < nameFilterNodes.getLength(); i++) {
Element element = (Element) nameFilterNodes.item(i);
String charaterPattern = UtilXml.childElementValue(element, ELEMENT_CHARACTER_PATTERN, null);
String replacement = UtilXml.childElementValue(element, ELEMENT_REPLACEMENT, null);
if (UtilValidate.isNotEmpty(charaterPattern)
&& UtilValidate.isNotEmpty(replacement)) {
try {
perlCompiler.compile(charaterPattern, Perl5Compiler.READ_ONLY_MASK);
charFilters.put(charaterPattern, replacement);
Debug.logInfo(" " + ELEMENT_CHARACTER_PATTERN + ": " + charaterPattern, module);
Debug.logInfo(" " + ELEMENT_REPLACEMENT + ": " + replacement, module);
} catch (MalformedPatternException e) {
// skip this filter (character-pattern replacement) if any error happened
Debug.logWarning(e, "Error while parsing " + ELEMENT_CHARACTER_PATTERN + ": " + charaterPattern, module);
}
}
}
} catch (NullPointerException e) {
// no "char-filters" element
Debug.logWarning("No " + ELEMENT_CHAR_FILTER + " element found in " + seoConfigFilename.toString(), module);
}
} catch (SAXException e) {
result = "error";
Debug.logError(e, module);
} catch (ParserConfigurationException e) {
result = "error";
Debug.logError(e, module);
} catch (IOException e) {
result = "error";
Debug.logError(e, module);
} finally {
if (configFileIS != null) {
try {
configFileIS.close();
} catch (IOException e) {
result = "error";
Debug.logError(e, module);
}
}
}
if (seoReplacements.keySet().isEmpty()) {
useUrlRegexp = false;
} else {
useUrlRegexp = true;
}
if (result.equals("success")) {
isInitialed = true;
}
}
/**
* Check whether the configuration file has been read.
*
* @return a boolean value to indicate whether the configuration file has been read.
*/
public static boolean isInitialed() {
return isInitialed;
}
/**
* Check whether url regexp should be used.
*
* @return a boolean value to indicate whether url regexp should be used.
*/
public static boolean checkUseUrlRegexp() {
return useUrlRegexp;
}
/**
* Get the general regexp pattern.
*
* @return the general regexp pattern.
*/
public static Pattern getGeneralRegexpPattern() {
return regexpIfMatch;
}
/**
* Check whether category url is enabled.
*
* @return a boolean value to indicate whether category url is enabled.
*/
public static boolean checkCategoryUrl() {
return categoryUrlEnabled;
}
/**
* Check whether the context path is enabled.
*
* @return a boolean value to indicate whether the context path is enabled.
*/
public static boolean isCategoryUrlEnabled(String contextPath) {
if (contextPath == null) {
return false;
}
if (UtilValidate.isEmpty(contextPath)) {
contextPath = "/";
}
if (categoryUrlEnabled) {
if (allowedContextPaths.contains(contextPath.trim())) {
return true;
} else {
return false;
}
}
return false;
}
/**
* Check whether category name is enabled.
*
* @return a boolean value to indicate whether category name is enabled.
*/
public static boolean isCategoryNameEnabled() {
return categoryNameEnabled;
}
/**
* Get category url suffix.
*
* @return String category url suffix.
*/
public static String getCategoryUrlSuffix() {
return categoryUrlSuffix;
}
/**
* Check whether jsessionid is enabled for anonymous.
*
* @return a boolean value to indicate whether jsessionid is enabled for anonymous.
*/
public static boolean isJSessionIdAnonEnabled() {
return jSessionIdAnonEnabled;
}
/**
* Check whether jsessionid is enabled for user.
*
* @return a boolean value to indicate whether jsessionid is enabled for user.
*/
public static boolean isJSessionIdUserEnabled() {
return jSessionIdUserEnabled;
}
/**
* Get user exception url pattern configures.
*
* @return user exception url pattern configures (java.util.List<Pattern>)
*/
public static List<Pattern> getUserExceptionPatterns() {
return userExceptionPatterns;
}
/**
* Get char filters.
*
* @return char filters (java.util.Map<String, String>)
*/
public static Map<String, String> getCharFilters() {
return charFilters;
}
/**
* Get seo url pattern configures.
*
* @return seo url pattern configures (java.util.Map<String, Pattern>)
*/
public static Map<String, Pattern> getSeoPatterns() {
return seoPatterns;
}
/**
* Get seo replacement configures.
*
* @return seo replacement configures (java.util.Map<String, String>)
*/
public static Map<String, String> getSeoReplacements() {
return seoReplacements;
}
/**
* Get forward replacement configures.
*
* @return forward replacement configures (java.util.Map<String, String>)
*/
public static Map<String, String> getForwardReplacements() {
return forwardReplacements;
}
/**
* Get forward response codes.
*
* @return forward response code configures (java.util.Map<String, Integer>)
*/
public static Map<String, Integer> getForwardResponseCodes() {
return forwardResponseCodes;
}
/**
* Check whether a product id is in the special list. If we cannot get a product from a lower cased
* or upper cased product id, then it's special.
*
* @return boolean to indicate whether the product id is special.
*/
@Deprecated
public static boolean isSpecialProductId(String productId) {
return specialProductIds.containsKey(productId);
}
/**
* Add a special product id to the special list.
*
* @param productId a product id get from database.
* @return true to indicate it has been added to special product id; false to indicate it's not special.
* @throws Exception to indicate there's already same lower cased product id in the list but value is a different product id.
*/
@Deprecated
public static boolean addSpecialProductId(String productId) throws Exception {
if (productId.toLowerCase().equals(productId) || productId.toUpperCase().equals(productId)) {
return false;
}
if (isSpecialProductId(productId.toLowerCase())) {
if (specialProductIds.containsValue(productId)) {
return true;
} else {
throw new Exception("This product Id cannot be lower cased for SEO URL purpose: " + productId);
}
}
specialProductIds.put(productId.toLowerCase(), productId);
return true;
}
/**
* Get a product id is in the special list.
*
* @return String of the original product id
*/
@Deprecated
public static String getSpecialProductId(String productId) {
return specialProductIds.get(productId);
}
}