Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java?rev=1535171&view=auto ============================================================================== --- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java (added) +++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java Wed Oct 23 20:48:36 2013 @@ -0,0 +1,498 @@ +/******************************************************************************* + * 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.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 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.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 debug = 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, Pattern> forwardPatterns = null; + private static Map<String, Integer> forwardResponseCodes = null; + private static Map<String, String> nameFilters = 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_DEBUG = "debug"; + public static final String ELEMENT_CONFIG = "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_NAME_FILTERS = "name-filters"; + public static final String ELEMENT_FILTER = "filter"; + public static final String ELEMENT_CHARACTER_PATTERN = "character-pattern"; + 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>(); + forwardPatterns = new HashMap<String, Pattern>(); + forwardResponseCodes = new HashMap<String, Integer>(); + userExceptionPatterns = FastList.newInstance(); + specialProductIds = FastMap.newInstance(); + nameFilters = FastMap.newInstance(); + try { + Document configDoc = UtilXml.readXmlDocument(UtilURL.fromResource(SEO_CONFIG_FILENAME), false); + Element rootElement = configDoc.getDocumentElement(); + + String regexIfMatch = UtilXml.childElementValue(rootElement, + ELEMENT_REGEXPIFMATCH, DEFAULT_REGEXP); + try { + regexpIfMatch = perlCompiler.compile(regexIfMatch, + Perl5Compiler.DEFAULT_MASK); + } catch (MalformedPatternException e1) { + // do nothing + } + debug = Boolean.parseBoolean(UtilXml.childElementValue( + rootElement, ELEMENT_DEBUG, "false")); + + // parse jsessionid element + try { + Element jSessionId = UtilXml.firstChildElement(rootElement, + ELEMENT_JSESSIONID); + 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; + } + } + + 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) { + List<? extends Element> exceptionUrlPatterns = UtilXml.childElementList(exceptions, ELEMENT_URLPATTERN); + for (int i = 0; i < exceptionUrlPatterns.size(); i++) { + Element element = (Element) exceptionUrlPatterns.get(i); + String urlpattern = element.getTextContent(); + if (UtilValidate.isNotEmpty(urlpattern)) { + try { + Pattern pattern = perlCompiler.compile( + urlpattern, Perl5Compiler.DEFAULT_MASK); + userExceptionPatterns.add(pattern); + } catch (MalformedPatternException e) { + // skip this url replacement if any error happened + } + } + } + } + } + } + } catch (NullPointerException e) { + // no "jsessionid" element + } + + // parse name-filters elements + try { + NodeList nameFilterNodes = rootElement + .getElementsByTagName(ELEMENT_FILTER); + 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.DEFAULT_MASK); + nameFilters.put(charaterPattern, + replacement); + } catch (MalformedPatternException e) { + // skip this filter (character-pattern replacement) if any error happened + } + } + } + } catch (NullPointerException e) { + // no "name-filters" element + } + + // parse config elements + try { + // construct seo patterns + NodeList seos = rootElement + .getElementsByTagName(ELEMENT_SEO); + for (int i = 0; i < seos.getLength(); i++) { + Element element = (Element) seos.item(i); + String urlpattern = UtilXml.childElementValue(element, + ELEMENT_URLPATTERN, null); + String replacement = UtilXml.childElementValue(element, + ELEMENT_REPLACEMENT, null); + if (UtilValidate.isNotEmpty(urlpattern) + && UtilValidate.isNotEmpty(replacement)) { + try { + Pattern pattern = perlCompiler.compile( + urlpattern, Perl5Compiler.DEFAULT_MASK); + seoReplacements.put(urlpattern, + replacement); + seoPatterns.put(urlpattern, pattern); + } catch (MalformedPatternException e) { + // skip this url replacement if any error happened + } + } + } + + // construct forward patterns + NodeList forwards = rootElement + .getElementsByTagName(ELEMENT_FORWARD); + for (int i = 0; i < forwards.getLength(); i++) { + Element element = (Element) forwards.item(i); + String urlpattern = UtilXml.childElementValue(element, + ELEMENT_URLPATTERN, null); + String replacement = UtilXml.childElementValue(element, + ELEMENT_REPLACEMENT, null); + String responseCode = UtilXml.childElementValue(element, + ELEMENT_RESPONSECODE, String.valueOf(DEFAULT_RESPONSECODE)); + if (UtilValidate.isNotEmpty(urlpattern) + && UtilValidate.isNotEmpty(replacement)) { + try { + Pattern pattern = perlCompiler.compile( + urlpattern, Perl5Compiler.DEFAULT_MASK); + forwardReplacements.put(urlpattern, + replacement); + forwardPatterns.put(urlpattern, pattern); + if (UtilValidate.isNotEmpty(responseCode)) { + Integer responseCodeInt = DEFAULT_RESPONSECODE; + try { + responseCodeInt = Integer.valueOf(responseCode); + } catch (NumberFormatException nfe) { + // do nothing + } + forwardResponseCodes.put(urlpattern, responseCodeInt); + } + } catch (MalformedPatternException e) { + // skip this url replacement if any error happened + } + } + } + + } catch (NullPointerException e) { + // no "config" element + } + } 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 debug is enabled. + * + * @return a boolean value to indicate whether debug is enabled. + */ + public static boolean isDebugEnabled() { + return debug; + } + + /** + * 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 name filters. + * + * @return name filters (java.util.Map<String, String>) + */ + public static Map<String, String> getNameFilters() { + return nameFilters; + } + + /** + * 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 url pattern configures. + * + * @return forward url pattern configures (java.util.Map<String, Pattern>) + */ + public static Map<String, Pattern> getForwardPatterns() { + return forwardPatterns; + } + + /** + * 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. + */ + 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. + */ + 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 + */ + public static String getSpecialProductId(String productId) { + return specialProductIds.get(productId); + } + +} Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java?rev=1535171&view=auto ============================================================================== --- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java (added) +++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java Wed Oct 23 20:48:36 2013 @@ -0,0 +1,172 @@ +/******************************************************************************* + * 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.IOException; +import java.util.List; +import java.util.Set; + +import javax.servlet.FilterChain; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import javolution.util.FastList; + +import org.ofbiz.base.util.Debug; +import org.ofbiz.base.util.StringUtil; +import org.ofbiz.base.util.UtilHttp; +import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilValidate; +import org.ofbiz.common.UrlServletHelper; +import org.ofbiz.entity.Delegator; +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.condition.EntityCondition; +import org.ofbiz.entity.condition.EntityOperator; +import org.ofbiz.entity.util.EntityUtil; +import org.ofbiz.webapp.control.ContextFilter; +import org.owasp.esapi.errors.EncodingException; + +public class SeoContentUrlFilter extends ContextFilter { + public final static String module = SeoContentUrlFilter.class.getName(); + protected static String defaultLocaleString = null; + protected static String redirectUrl = null; + public static String defaultViewRequest = "contentViewInfo"; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + Delegator delegator = (Delegator) httpRequest.getSession().getServletContext().getAttribute("delegator"); + + // Get ServletContext + ServletContext servletContext = config.getServletContext(); + // Set request attribute and session + UrlServletHelper.setRequestAttributes(request, delegator, servletContext); + String urlContentId = null; + StringBuffer pathInfoBuffer = UtilHttp.getFullRequestUrl(httpRequest); + String pathInfo = pathInfoBuffer.toString(); + if (UtilValidate.isNotEmpty(pathInfo)) { + String alternativeUrl = pathInfo.substring(pathInfo.lastIndexOf("/")); + if (alternativeUrl.endsWith("-content")) { + try { + List<GenericValue> contentDataResourceViews = delegator.findByAnd("ContentDataResourceView", UtilMisc.toMap("drObjectInfo", alternativeUrl), null, false); + if (contentDataResourceViews.size() > 0) { + contentDataResourceViews = EntityUtil.orderBy(contentDataResourceViews, UtilMisc.toList("createdDate DESC")); + GenericValue contentDataResourceView = EntityUtil.getFirst(contentDataResourceViews); + List<GenericValue> contents = EntityUtil.filterByDate(delegator.findByAnd("ContentAssoc", + UtilMisc.toMap("contentAssocTypeId", "ALTERNATIVE_URL", "contentIdTo", contentDataResourceView.getString("contentId")), null, false)); + if (contents.size() > 0) { + GenericValue content = EntityUtil.getFirst(contents); + urlContentId = content.getString("contentId"); + } + } + } catch (Exception e) { + Debug.logWarning(e.getMessage(), module); + } + } + if (UtilValidate.isNotEmpty(urlContentId)) { + StringBuilder urlBuilder = new StringBuilder(); + if (UtilValidate.isNotEmpty(SeoControlServlet.controlServlet)) { + urlBuilder.append("/" + SeoControlServlet.controlServlet); + } + urlBuilder.append("/" + config.getInitParameter("viewRequest") + "?contentId=" + urlContentId); + + // Set view query parameters + UrlServletHelper.setViewQueryParameters(request, urlBuilder); + Debug.logInfo("[Filtered request]: " + pathInfo + " (" + urlBuilder + ")", module); + RequestDispatcher dispatch = request.getRequestDispatcher(urlBuilder.toString()); + dispatch.forward(request, response); + return; + } + + // Check path alias + UrlServletHelper.checkPathAlias(request, httpResponse, delegator, pathInfo); + } + // we're done checking; continue on + chain.doFilter(request, response); + } + + public static String makeContentAltUrl(HttpServletRequest request, HttpServletResponse response, String contentId, String viewContent) { + if (UtilValidate.isEmpty(contentId)) { + return null; + } + Delegator delegator = (Delegator) request.getAttribute("delegator"); + String url = null; + try { + List<EntityCondition> expr = FastList.newInstance(); + expr.add(EntityCondition.makeCondition("caContentAssocTypeId", EntityOperator.EQUALS, "ALTERNATIVE_URL")); + expr.add(EntityCondition.makeCondition("caThruDate", EntityOperator.EQUALS, null)); + expr.add(EntityCondition.makeCondition("contentIdStart", EntityOperator.EQUALS, contentId)); + Set<String> fieldsToSelect = UtilMisc.toSet("contentIdStart", "drObjectInfo", "dataResourceId", "caFromDate", "caThruDate", "caCreatedDate"); + List<GenericValue> contentAssocDataResources = delegator.findList("ContentAssocDataResourceViewTo", EntityCondition.makeCondition(expr), fieldsToSelect, + UtilMisc.toList("-caFromDate"), null, true); + if (contentAssocDataResources.size() > 0) { + GenericValue contentAssocDataResource = EntityUtil.getFirst(contentAssocDataResources); + url = contentAssocDataResource.getString("drObjectInfo"); + try { + url = StringUtil.defaultWebEncoder.decodeFromURL(url); + String mountPoint = request.getContextPath(); + if (!(mountPoint.equals("/")) && !(mountPoint.equals(""))) { + url = mountPoint + url; + } + } catch (EncodingException e) { + Debug.logError(e, module); + } + } + } catch (Exception e) { + Debug.logWarning("[Exception] : " + e.getMessage(), module); + } + + if (UtilValidate.isEmpty(url)) { + if (UtilValidate.isEmpty(viewContent)) { + viewContent = defaultViewRequest; + } + url = makeContentUrl(request, response, contentId, viewContent); + } + return url; + } + + public static String makeContentUrl(HttpServletRequest request, HttpServletResponse response, String contentId, String viewContent) { + if (UtilValidate.isEmpty(contentId)) { + return null; + } + StringBuilder urlBuilder = new StringBuilder(); + urlBuilder.append(request.getSession().getServletContext().getContextPath()); + if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') { + urlBuilder.append("/"); + } + if (UtilValidate.isNotEmpty(SeoControlServlet.controlServlet)) { + urlBuilder.append(SeoControlServlet.controlServlet + "/"); + } + + if (UtilValidate.isNotEmpty(viewContent)) { + urlBuilder.append(viewContent); + } else { + urlBuilder.append(defaultViewRequest); + } + urlBuilder.append("?contentId=" + contentId); + return urlBuilder.toString(); + } +} Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java?rev=1535171&view=auto ============================================================================== --- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java (added) +++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java Wed Oct 23 20:48:36 2013 @@ -0,0 +1,370 @@ +/******************************************************************************* + * 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 static org.ofbiz.base.util.UtilGenerics.checkMap; + +import java.io.IOException; +import java.net.URL; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.FilterChain; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Matcher; +import org.ofbiz.base.util.Debug; +import org.ofbiz.base.util.StringUtil; +import org.ofbiz.base.util.UtilHttp; +import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilObject; +import org.ofbiz.base.util.UtilProperties; +import org.ofbiz.base.util.UtilValidate; +import org.ofbiz.entity.Delegator; +import org.ofbiz.entity.DelegatorFactory; +import org.ofbiz.entity.GenericEntityException; +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.condition.EntityCondition; +import org.ofbiz.entity.util.EntityUtil; +import org.ofbiz.security.Security; +import org.ofbiz.service.LocalDispatcher; +import org.ofbiz.webapp.control.ConfigXMLReader; +import org.ofbiz.webapp.control.ConfigXMLReader.ControllerConfig; +import org.ofbiz.webapp.control.ContextFilter; +import org.ofbiz.webapp.control.WebAppConfigurationException; +import org.ofbiz.webapp.website.WebSiteWorker; + +/** + * SeoContextFilter - Restricts access to raw files and configures servlet objects. + */ +public class SeoContextFilter extends ContextFilter { + + public static final String module = SeoContextFilter.class.getName(); + + /** + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String uri = httpRequest.getRequestURI(); + boolean forwarded = forwardUri(httpResponse, uri); + if (forwarded) { + return; + } + + URL controllerConfigURL = ConfigXMLReader.getControllerConfigURL(config.getServletContext()); + ControllerConfig controllerConfig = null; + Map<String, ConfigXMLReader.RequestMap> requestMaps = null; + try { + controllerConfig = ConfigXMLReader.getControllerConfig(controllerConfigURL); + requestMaps = controllerConfig.getRequestMapMap(); + } catch (WebAppConfigurationException e) { + Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module); + throw new ServletException(e); + } + Set<String> uris = requestMaps.keySet(); + + // NOTE: the following part is copied from org.ofbiz.webapp.control.ContextFilter.doFilter method, please update this if framework is updated. + // Debug.logInfo("Running ContextFilter.doFilter", module); + + // ----- Servlet Object Setup ----- + // set the cached class loader for more speedy running in this thread + String disableCachedClassloader = config.getInitParameter("disableCachedClassloader"); + if (disableCachedClassloader == null || !"Y".equalsIgnoreCase(disableCachedClassloader)) { + Thread.currentThread().setContextClassLoader(localCachedClassLoader); + } + + // set the ServletContext in the request for future use + httpRequest.setAttribute("servletContext", config.getServletContext()); + + // set the webSiteId in the session + if (UtilValidate.isEmpty(httpRequest.getSession().getAttribute("webSiteId"))) { + httpRequest.getSession().setAttribute("webSiteId", WebSiteWorker.getWebSiteId(httpRequest)); + } + + // set the filesystem path of context root. + httpRequest.setAttribute("_CONTEXT_ROOT_", config.getServletContext().getRealPath("/")); + + // set the server root url + StringBuffer serverRootUrl = UtilHttp.getServerRootUrl(httpRequest); + httpRequest.setAttribute("_SERVER_ROOT_URL_", serverRootUrl.toString()); + + // request attributes from redirect call + String reqAttrMapHex = (String) httpRequest.getSession().getAttribute("_REQ_ATTR_MAP_"); + if (UtilValidate.isNotEmpty(reqAttrMapHex)) { + byte[] reqAttrMapBytes = StringUtil.fromHexString(reqAttrMapHex); + Map<String, Object> reqAttrMap = checkMap(UtilObject.getObject(reqAttrMapBytes), String.class, Object.class); + if (reqAttrMap != null) { + for (Map.Entry<String, Object> entry : reqAttrMap.entrySet()) { + httpRequest.setAttribute(entry.getKey(), entry.getValue()); + } + } + httpRequest.getSession().removeAttribute("_REQ_ATTR_MAP_"); + } + + // ----- Context Security ----- + // check if we are disabled + String disableSecurity = config.getInitParameter("disableContextSecurity"); + if (disableSecurity != null && "Y".equalsIgnoreCase(disableSecurity)) { + chain.doFilter(httpRequest, httpResponse); + return; + } + + // check if we are told to redirect everthing + String redirectAllTo = config.getInitParameter("forceRedirectAll"); + if (UtilValidate.isNotEmpty(redirectAllTo)) { + // little trick here so we don't loop on ourself + if (httpRequest.getSession().getAttribute("_FORCE_REDIRECT_") == null) { + httpRequest.getSession().setAttribute("_FORCE_REDIRECT_", "true"); + Debug.logWarning("Redirecting user to: " + redirectAllTo, module); + + if (!redirectAllTo.toLowerCase().startsWith("http")) { + redirectAllTo = httpRequest.getContextPath() + redirectAllTo; + } + httpResponse.sendRedirect(redirectAllTo); + return; + } else { + httpRequest.getSession().removeAttribute("_FORCE_REDIRECT_"); + chain.doFilter(httpRequest, httpResponse); + return; + } + } + + // test to see if we have come through the control servlet already, if not do the processing + String requestPath = null; + String contextUri = null; + if (httpRequest.getAttribute(ContextFilter.FORWARDED_FROM_SERVLET) == null) { + // Debug.logInfo("In ContextFilter.doFilter, FORWARDED_FROM_SERVLET is NOT set", module); + String allowedPath = config.getInitParameter("allowedPaths"); + String redirectPath = config.getInitParameter("redirectPath"); + String errorCode = config.getInitParameter("errorCode"); + + List<String> allowList = StringUtil.split(allowedPath, ":"); + allowList.add("/"); // No path is allowed. + if (UtilValidate.isNotEmpty(httpRequest.getServletPath())) { + allowList.add(""); // No path is allowed if servlet path is not empty. + } + + if (debug) Debug.logInfo("[Domain]: " + httpRequest.getServerName() + " [Request]: " + httpRequest.getRequestURI(), module); + + requestPath = httpRequest.getServletPath(); + if (requestPath == null) requestPath = ""; + if (requestPath.lastIndexOf("/") > 0) { + if (requestPath.indexOf("/") == 0) { + requestPath = "/" + requestPath.substring(1, requestPath.indexOf("/", 1)); + } else { + requestPath = requestPath.substring(1, requestPath.indexOf("/")); + } + } + + String requestInfo = httpRequest.getServletPath(); + if (requestInfo == null) requestInfo = ""; + if (requestInfo.lastIndexOf("/") >= 0) { + requestInfo = requestInfo.substring(0, requestInfo.lastIndexOf("/")) + "/*"; + } + + StringBuilder contextUriBuffer = new StringBuilder(); + if (httpRequest.getContextPath() != null) { + contextUriBuffer.append(httpRequest.getContextPath()); + } + if (httpRequest.getServletPath() != null) { + contextUriBuffer.append(httpRequest.getServletPath()); + } + if (httpRequest.getPathInfo() != null) { + contextUriBuffer.append(httpRequest.getPathInfo()); + } + contextUri = contextUriBuffer.toString(); + + List<String> pathItemList = StringUtil.split(httpRequest.getPathInfo(), "/"); + String viewName = ""; + if (pathItemList != null) { + viewName = pathItemList.get(0); + } + + // Verbose Debugging + if (Debug.verboseOn()) { + for (String allow : allowList) { + Debug.logVerbose("[Allow]: " + allow, module); + } + Debug.logVerbose("[Request path]: " + requestPath, module); + Debug.logVerbose("[Request info]: " + requestInfo, module); + Debug.logVerbose("[Servlet path]: " + httpRequest.getServletPath(), module); + Debug.logVerbose( + "[Not In AllowList]: " + (!allowList.contains(requestPath) && !allowList.contains(requestInfo) && !allowList.contains(httpRequest.getServletPath())), + module); + Debug.logVerbose("[Not In controller]: " + (UtilValidate.isEmpty(requestPath) && UtilValidate.isEmpty(httpRequest.getServletPath()) && !uris.contains(viewName)), + module); + } + + // check to make sure the requested url is allowed + if (!allowList.contains(requestPath) && !allowList.contains(requestInfo) && !allowList.contains(httpRequest.getServletPath()) + && (UtilValidate.isEmpty(requestPath) && UtilValidate.isEmpty(httpRequest.getServletPath()) && !uris.contains(viewName))) { + String filterMessage = "[Filtered request]: " + contextUri; + + if (redirectPath == null) { + if (UtilValidate.isEmpty(viewName)) { + // redirect without any url change in browser + RequestDispatcher rd = request.getRequestDispatcher(SeoControlServlet.defaultPage); + rd.forward(request, response); + } else { + int error = 404; + if (UtilValidate.isNotEmpty(errorCode)) { + try { + error = Integer.parseInt(errorCode); + } catch (NumberFormatException nfe) { + Debug.logWarning(nfe, "Error code specified would not parse to Integer : " + errorCode, module); + } + } + filterMessage = filterMessage + " (" + error + ")"; + httpResponse.sendError(error, contextUri); + request.setAttribute("filterRequestUriError", contextUri); + } + } else { + filterMessage = filterMessage + " (" + redirectPath + ")"; + if (!redirectPath.toLowerCase().startsWith("http")) { + redirectPath = httpRequest.getContextPath() + redirectPath; + } + // httpResponse.sendRedirect(redirectPath); + if (uri.equals("") || uri.equals("/")) { + // redirect without any url change in browser + RequestDispatcher rd = request.getRequestDispatcher(redirectPath); + rd.forward(request, response); + } else { + // redirect with url change in browser + httpResponse.setStatus(SeoConfigUtil.DEFAULT_RESPONSECODE); + httpResponse.setHeader("Location", redirectPath); + } + } + Debug.logWarning(filterMessage, module); + return; + } + } + + // check if multi tenant is enabled + String useMultitenant = UtilProperties.getPropertyValue("general.properties", "multitenant"); + if ("Y".equals(useMultitenant)) { + // get tenant delegator by domain name + String serverName = httpRequest.getServerName(); + try { + // if tenant was specified, replace delegator with the new per-tenant delegator and set tenantId to session attribute + Delegator delegator = getDelegator(config.getServletContext()); + List<GenericValue> tenants = delegator.findList("Tenant", EntityCondition.makeCondition("domainName", serverName), null, UtilMisc.toList("-createdStamp"), null, false); + if (UtilValidate.isNotEmpty(tenants)) { + GenericValue tenant = EntityUtil.getFirst(tenants); + String tenantId = tenant.getString("tenantId"); + + // if the request path is a root mount then redirect to the initial path + if (UtilValidate.isNotEmpty(requestPath) && requestPath.equals(contextUri)) { + String initialPath = tenant.getString("initialPath"); + if (UtilValidate.isNotEmpty(initialPath) && !"/".equals(initialPath)) { + ((HttpServletResponse) response).sendRedirect(initialPath); + return; + } + } + + // make that tenant active, setup a new delegator and a new dispatcher + String tenantDelegatorName = delegator.getDelegatorBaseName() + "#" + tenantId; + httpRequest.getSession().setAttribute("delegatorName", tenantDelegatorName); + + // after this line the delegator is replaced with the new per-tenant delegator + delegator = DelegatorFactory.getDelegator(tenantDelegatorName); + config.getServletContext().setAttribute("delegator", delegator); + + // clear web context objects + config.getServletContext().setAttribute("security", null); + config.getServletContext().setAttribute("dispatcher", null); + + // initialize security + Security security = getSecurity(); + // initialize the services dispatcher + LocalDispatcher dispatcher = getDispatcher(config.getServletContext()); + + // set web context objects + request.setAttribute("dispatcher", dispatcher); + request.setAttribute("security", security); + + request.setAttribute("tenantId", tenantId); + } + + // NOTE DEJ20101130: do NOT always put the delegator name in the user's session because the user may + // have logged in and specified a tenant, and even if no Tenant record with a matching domainName field + // is found this will change the user's delegator back to the base one instead of the one for the + // tenant specified on login + // httpRequest.getSession().setAttribute("delegatorName", delegator.getDelegatorName()); + } catch (GenericEntityException e) { + Debug.logWarning(e, "Unable to get Tenant", module); + } + } + + // we're done checking; continue on + chain.doFilter(httpRequest, httpResponse); + } + + /** + * Forward a uri according to forward pattern regular expressions. Note: this is developed for Filter usage. + * + * @param uri String to reverse transform + * @return String + */ + protected static boolean forwardUri(HttpServletResponse response, String uri) { + Perl5Matcher matcher = new Perl5Matcher(); + boolean foundMatch = false; + Integer responseCodeInt = null; + + if (SeoConfigUtil.checkUseUrlRegexp() && SeoConfigUtil.getForwardPatterns() != null && SeoConfigUtil.getForwardReplacements() != null) { + Iterator<String> keys = SeoConfigUtil.getForwardPatterns().keySet().iterator(); + while (keys.hasNext()) { + String key = keys.next(); + Pattern pattern = SeoConfigUtil.getForwardPatterns().get(key); + String replacement = SeoConfigUtil.getForwardReplacements().get(key); + if (matcher.matches(uri, pattern)) { + for (int i = matcher.getMatch().groups(); i > 0; i--) { + replacement = replacement.replaceAll("\\$" + i, matcher.getMatch().group(i)); + } + uri = replacement; + responseCodeInt = SeoConfigUtil.getForwardResponseCodes().get(key); + foundMatch = true; + // be careful, we don't break after finding a match + } + } + } + + if (foundMatch) { + if (responseCodeInt == null) { + response.setStatus(SeoConfigUtil.DEFAULT_RESPONSECODE); + } else { + response.setStatus(responseCodeInt.intValue()); + } + response.setHeader("Location", uri); + } else if (SeoConfigUtil.isDebugEnabled()) { + Debug.logInfo("Can NOT forward this url: " + uri, module); + } + return foundMatch; + } +} Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java?rev=1535171&view=auto ============================================================================== --- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java (added) +++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java Wed Oct 23 20:48:36 2013 @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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 javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.ofbiz.base.util.UtilValidate; +import org.ofbiz.webapp.control.ControlServlet; + +/** + * SeoControlServlet.java - SEO Master servlet for the web application. + */ +@SuppressWarnings("serial") +public class SeoControlServlet extends ControlServlet { + + public static final String module = SeoControlServlet.class.getName(); + + protected static String defaultPage = null; + protected static String controlServlet = null; + + public SeoControlServlet() { + super(); + } + + /** + * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) + */ + public void init(ServletConfig config) throws ServletException { + super.init(config); + + ServletContext context = this.getServletContext(); + if (UtilValidate.isEmpty(defaultPage)) { + defaultPage = context.getInitParameter("defaultPage"); + } + if (UtilValidate.isEmpty(defaultPage)) { + defaultPage = "/main"; + } + + if (defaultPage.startsWith("/") && defaultPage.lastIndexOf("/") > 0) { + controlServlet = defaultPage.substring(1); + controlServlet = controlServlet.substring(0, controlServlet.indexOf("/")); + } + + SeoConfigUtil.init(); + } +} Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java?rev=1535171&view=auto ============================================================================== --- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java (added) +++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java Wed Oct 23 20:48:36 2013 @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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 org.ofbiz.base.util.UtilValidate; + +public class SeoUrlUtil { + public static String replaceSpecialCharsUrl(String url) { + if (UtilValidate.isEmpty(url)) { + url = ""; + } + for (String characterPattern : SeoConfigUtil.getNameFilters().keySet()) { + url = url.replaceAll(characterPattern, SeoConfigUtil.getNameFilters().get(characterPattern)); + } + return url; + } + + public static String removeContextPath(String uri, String contextPath) { + if (UtilValidate.isEmpty(contextPath) || UtilValidate.isEmpty(uri)) { + return uri; + } + if (uri.length() > contextPath.length() && uri.startsWith(contextPath)) { + return uri.substring(contextPath.length()); + } + return uri; + } +} Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpConfigUtil.java URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpConfigUtil.java?rev=1535171&view=auto ============================================================================== --- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpConfigUtil.java (added) +++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpConfigUtil.java Wed Oct 23 20:48:36 2013 @@ -0,0 +1,526 @@ +/******************************************************************************* + * 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.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; + +/** + * UrlRegexpConfigUtil - Configuration file utility. + * + */ +public class UrlRegexpConfigUtil { + + private static final String module = UrlRegexpConfigUtil.class.getName(); + private static Perl5Compiler m_perlCompiler = new Perl5Compiler(); + private static boolean m_isInitialed = false; + private static boolean m_debug = false; + private static boolean m_categoryUrlEnabled = true; + private static boolean m_categoryNameEnabled = false; + private static String m_categoryUrlSuffix = null; + private static Pattern m_regexpIfMatch = null; + private static boolean m_useUrlRegexp = false; + private static boolean m_jSessionIdAnonEnabled = false; + private static boolean m_jSessionIdUserEnabled = false; + private static Map<String, String> m_seoReplacements = null; + private static Map<String, Pattern> m_seoPatterns = null; + private static Map<String, String> m_forwardReplacements = null; + private static Map<String, Pattern> m_forwardPatterns = null; + private static Map<String, Integer> m_forwardResponseCodes = null; + private static Map<String, String> m_nameFilters = null; + private static List<Pattern> m_userExceptionPatterns = null; + private static Set<String> m_allowedContextPaths = null; + private static Map<String, String> m_specialProductIds = null; + public static final String DEFAULT_REGEXP = "^.*/.*$"; + public static final String ELEMENT_REGEXPIFMATCH = "regexpifmatch"; + public static final String ELEMENT_DEBUG = "debug"; + 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 ELEMENT_CONFIG = "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_NAME_FILTERS = "name-filters"; + public static final String ELEMENT_FILTER = "filter"; + public static final String ELEMENT_CHARACTER_PATTERN = "character-pattern"; + public static final String URL_REGEXP_CONFIG_FILENAME = "urlregexp.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"; + m_seoPatterns = new HashMap<String, Pattern>(); + m_seoReplacements = new HashMap<String, String>(); + m_forwardReplacements = new HashMap<String, String>(); + m_forwardPatterns = new HashMap<String, Pattern>(); + m_forwardResponseCodes = new HashMap<String, Integer>(); + m_userExceptionPatterns = FastList.newInstance(); + m_specialProductIds = FastMap.newInstance(); + m_nameFilters = FastMap.newInstance(); + try { + Document configDoc = UtilXml.readXmlDocument(UtilURL.fromResource(URL_REGEXP_CONFIG_FILENAME), false); + Element rootElement = configDoc.getDocumentElement(); + + String regexIfMatch = UtilXml.childElementValue(rootElement, ELEMENT_REGEXPIFMATCH, DEFAULT_REGEXP); + try { + m_regexpIfMatch = m_perlCompiler.compile(regexIfMatch, Perl5Compiler.DEFAULT_MASK); + } catch (MalformedPatternException e1) { + // do nothing + } + m_debug = Boolean.parseBoolean(UtilXml.childElementValue(rootElement, ELEMENT_DEBUG, "false")); + + // parse category-url element + try { + Element categoryUrlElement = UtilXml.firstChildElement(rootElement, ELEMENT_CATEGORY_URL); + if (categoryUrlElement != null) { + String enableCategoryUrlValue = UtilXml.childElementValue(categoryUrlElement, ELEMENT_VALUE, DEFAULT_CATEGORY_URL_VALUE); + if (DEFAULT_CATEGORY_URL_VALUE.equalsIgnoreCase(enableCategoryUrlValue)) { + m_categoryUrlEnabled = true; + } else { + m_categoryUrlEnabled = false; + } + + if (m_categoryUrlEnabled) { + String allowedContextValue = UtilXml.childElementValue(categoryUrlElement, ELEMENT_ALLOWED_CONTEXT_PATHS, null); + m_allowedContextPaths = FastSet.newInstance(); + if (UtilValidate.isNotEmpty(allowedContextValue)) { + List<String> allowedContextPaths = StringUtil.split(allowedContextValue, ALLOWED_CONTEXT_PATHS_SEPERATOR); + for (String path : allowedContextPaths) { + if (UtilValidate.isNotEmpty(path)) { + path = path.trim(); + if (!m_allowedContextPaths.contains(path)) { + m_allowedContextPaths.add(path); + } + } + } + } + + String categoryNameValue = UtilXml.childElementValue(categoryUrlElement, ELEMENT_CATEGORY_NAME, DEFAULT_CATEGORY_NAME_VALUE); + if (DEFAULT_CATEGORY_NAME_VALUE.equalsIgnoreCase(categoryNameValue)) { + m_categoryNameEnabled = false; + } else { + m_categoryNameEnabled = true; + } + + m_categoryUrlSuffix = UtilXml.childElementValue(categoryUrlElement, ELEMENT_CATEGORY_URL_SUFFIX, null); + if (UtilValidate.isNotEmpty(m_categoryUrlSuffix)) { + m_categoryUrlSuffix = m_categoryUrlSuffix.trim(); + if (m_categoryUrlSuffix.contains("/")) { + m_categoryUrlSuffix = null; + } + } + } + } + } catch (NullPointerException e) { + // no "category-url" element + } + + // parse jsessionid element + try { + Element jSessionId = UtilXml.firstChildElement(rootElement, ELEMENT_JSESSIONID); + 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)) { + m_jSessionIdAnonEnabled = false; + } else { + m_jSessionIdAnonEnabled = true; + } + } + + 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)) { + m_jSessionIdUserEnabled = false; + } else { + m_jSessionIdUserEnabled = true; + } + Element exceptions = UtilXml.firstChildElement(user, ELEMENT_EXCEPTIONS); + if (exceptions != null) { + List<? extends Element> exceptionUrlPatterns = UtilXml.childElementList(exceptions, ELEMENT_URLPATTERN); + for (int i = 0; i < exceptionUrlPatterns.size(); i++) { + Element element = (Element) exceptionUrlPatterns.get(i); + String urlpattern = element.getTextContent(); + if (UtilValidate.isNotEmpty(urlpattern)) { + try { + Pattern pattern = m_perlCompiler.compile(urlpattern, Perl5Compiler.DEFAULT_MASK); + m_userExceptionPatterns.add(pattern); + } catch (MalformedPatternException e) { + // skip this url replacement if any error happened + } + } + } + } + } + } + } catch (NullPointerException e) { + // no "jsessionid" element + } + + // parse name-filters elements + try { + NodeList nameFilters = rootElement.getElementsByTagName(ELEMENT_FILTER); + for (int i = 0; i < nameFilters.getLength(); i++) { + Element element = (Element) nameFilters.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 { + m_perlCompiler.compile(charaterPattern, Perl5Compiler.DEFAULT_MASK); + m_nameFilters.put(charaterPattern, replacement); + } catch (MalformedPatternException e) { + // skip this filter (character-pattern replacement) if any error happened + } + } + } + } catch (NullPointerException e) { + // no "name-filters" element + } + + // parse config elements + try { + // construct seo patterns + NodeList seos = rootElement.getElementsByTagName(ELEMENT_SEO); + for (int i = 0; i < seos.getLength(); i++) { + Element element = (Element) seos.item(i); + String urlpattern = UtilXml.childElementValue(element, ELEMENT_URLPATTERN, null); + String replacement = UtilXml.childElementValue(element, ELEMENT_REPLACEMENT, null); + if (UtilValidate.isNotEmpty(urlpattern) && UtilValidate.isNotEmpty(replacement)) { + try { + Pattern pattern = m_perlCompiler.compile(urlpattern, Perl5Compiler.DEFAULT_MASK); + m_seoReplacements.put(urlpattern, replacement); + m_seoPatterns.put(urlpattern, pattern); + } catch (MalformedPatternException e) { + // skip this url replacement if any error happened + } + } + } + + // construct forward patterns + NodeList forwards = rootElement.getElementsByTagName(ELEMENT_FORWARD); + for (int i = 0; i < forwards.getLength(); i++) { + Element element = (Element) forwards.item(i); + String urlpattern = UtilXml.childElementValue(element, ELEMENT_URLPATTERN, null); + String replacement = UtilXml.childElementValue(element, ELEMENT_REPLACEMENT, null); + String responseCode = UtilXml.childElementValue(element, ELEMENT_RESPONSECODE, String.valueOf(DEFAULT_RESPONSECODE)); + if (UtilValidate.isNotEmpty(urlpattern) && UtilValidate.isNotEmpty(replacement)) { + try { + Pattern pattern = m_perlCompiler.compile(urlpattern, Perl5Compiler.DEFAULT_MASK); + m_forwardReplacements.put(urlpattern, replacement); + m_forwardPatterns.put(urlpattern, pattern); + if (UtilValidate.isNotEmpty(responseCode)) { + Integer responseCodeInt = DEFAULT_RESPONSECODE; + try { + responseCodeInt = Integer.valueOf(responseCode); + } catch (NumberFormatException nfe) { + // do nothing + } + m_forwardResponseCodes.put(urlpattern, responseCodeInt); + } + } catch (MalformedPatternException e) { + // skip this url replacement if any error happened + } + } + } + + } catch (NullPointerException e) { + // no "config" element + } + } 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 (m_seoReplacements.keySet().isEmpty()) { + m_useUrlRegexp = false; + } else { + m_useUrlRegexp = true; + } + if (result.equals("success")) { + m_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 m_isInitialed; + } + + /** + * Check whether debug is enabled. + * + * @return a boolean value to indicate whether debug is enabled. + */ + public static boolean isDebugEnabled() { + return m_debug; + } + + /** + * Check whether url regexp should be used. + * + * @return a boolean value to indicate whether url regexp should be used. + */ + public static boolean checkUseUrlRegexp() { + return m_useUrlRegexp; + } + + /** + * Get the general regexp pattern. + * + * @return the general regexp pattern. + */ + public static Pattern getGeneralRegexpPattern() { + return m_regexpIfMatch; + } + + /** + * Check whether category url is enabled. + * + * @return a boolean value to indicate whether category url is enabled. + */ + public static boolean checkCategoryUrl() { + return m_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 (m_categoryUrlEnabled) { + if (m_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 m_categoryNameEnabled; + } + + /** + * Get category url suffix. + * + * @return String category url suffix. + */ + public static String getCategoryUrlSuffix() { + return m_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 m_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 m_jSessionIdUserEnabled; + } + + /** + * Get user exception url pattern configures. + * + * @return user exception url pattern configures (java.util.List<Pattern>) + */ + public static List<Pattern> getUserExceptionPatterns() { + return m_userExceptionPatterns; + } + + /** + * Get name filters. + * + * @return name filters (java.util.Map<String, String>) + */ + public static Map<String, String> getNameFilters() { + return m_nameFilters; + } + + /** + * Get seo url pattern configures. + * + * @return seo url pattern configures (java.util.Map<String, Pattern>) + */ + public static Map<String, Pattern> getSeoPatterns() { + return m_seoPatterns; + } + + /** + * Get seo replacement configures. + * + * @return seo replacement configures (java.util.Map<String, String>) + */ + public static Map<String, String> getSeoReplacements() { + return m_seoReplacements; + } + + /** + * Get forward url pattern configures. + * + * @return forward url pattern configures (java.util.Map<String, Pattern>) + */ + public static Map<String, Pattern> getForwardPatterns() { + return m_forwardPatterns; + } + + /** + * Get forward replacement configures. + * + * @return forward replacement configures (java.util.Map<String, String>) + */ + public static Map<String, String> getForwardReplacements() { + return m_forwardReplacements; + } + + /** + * Get forward response codes. + * + * @return forward response code configures (java.util.Map<String, Integer>) + */ + public static Map<String, Integer> getForwardResponseCodes() { + return m_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. + */ + public static boolean isSpecialProductId(String productId) { + return m_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. + */ + public static boolean addSpecialProductId(String productId) throws Exception { + if (productId.toLowerCase().equals(productId) || productId.toUpperCase().equals(productId)) { + return false; + } + if (isSpecialProductId(productId.toLowerCase())) { + if (m_specialProductIds.containsValue(productId)) { + return true; + } else { + throw new Exception("This product Id cannot be lower cased for SEO URL purpose: " + productId); + } + } + m_specialProductIds.put(productId.toLowerCase(), productId); + return true; + } + + /** + * Get a product id is in the special list. + * + * @return String of the original product id + */ + public static String getSpecialProductId(String productId) { + return m_specialProductIds.get(productId); + } + +} Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpConfigUtil.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpConfigUtil.java ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpConfigUtil.java ------------------------------------------------------------------------------ svn:mime-type = text/plain |
Free forum by Nabble | Edit this page |