Keeps in sync with OFBiz trunk HEAD
git-svn-id: https://svn.apache.org/repos/asf/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23@1651038 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/applications/product/build.xml b/applications/product/build.xml
index da199ea..03a8430 100644
--- a/applications/product/build.xml
+++ b/applications/product/build.xml
@@ -44,10 +44,12 @@
<fileset dir="../../framework/webapp/lib" includes="*.jar"/>
<fileset dir="../../framework/webapp/build/lib" includes="*.jar"/>
<fileset dir="../../framework/common/build/lib" includes="*.jar"/>
+ <fileset dir="../../framework/base/lib/scripting" includes="*.jar"/>
<fileset dir="../content/lib" includes="*.jar"/>
<fileset dir="../content/build/lib" includes="*.jar"/>
<fileset dir="../party/build/lib" includes="*.jar"/>
<fileset dir="lib" includes="*.jar"/>
+ <fileset dir="../../framework/catalina/lib" includes="*.jar"/>
</path>
<target name="init">
diff --git a/applications/product/config/SeoConfig.xml b/applications/product/config/SeoConfig.xml
new file mode 100644
index 0000000..c670ebb
--- /dev/null
+++ b/applications/product/config/SeoConfig.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<seo-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../dtd/SeoConfig.xsd">
+ <regexpifmatch>^.*/.*$</regexpifmatch>
+
+ <category-url>
+ <value>enable</value>
+ <!-- enable/disable
+ 1. if enable, the product seo url will be /product-name-product-id.html or /category-name-product-name-product-id.html
+ 2. if disable, the CatalogUrlServlet.makeCatalogUrl will be used -->
+ <allowed-context-paths>/:/ecommerce</allowed-context-paths>
+ <!-- if category-url is enabled, only context paths listed here will be allowed to use the new seo transformers -->
+ <category-name>disable</category-name>
+ <!-- enable/disable
+ 1. if enable, the product seo url will be /category-name-product-name-product-id.html
+ 2. if disable, the product seo url will be /product-name-product-id.html -->
+ <category-url-suffix>.html</category-url-suffix>
+ <!-- suffix to add to the product seo url -->
+ </category-url>
+
+ <jsessionid>
+ <anonymous>
+ <value>disable</value>
+ <!-- enable/disable
+ 1. when disable, the seo url patterns that contains jsessionid will be applied;
+ 2. when enable, the seo url patterns that contains jsessionid will be skipped -->
+ </anonymous>
+ <user>
+ <value>disable</value>
+ <!-- enable/disable
+ 1. when disable, the url-patterns under exceptions will be used to skip the seo url patterns that contains jsessionid;
+ 2. when enable, the seo url patterns that contains jsessionid will be skipped -->
+ <exceptions>
+ <url-pattern>^.*/(keywordsearch|logout).*$</url-pattern>
+ <!-- sample: ^.*/(keywordsearch|logout).*$ -->
+ </exceptions>
+ </user>
+ </jsessionid>
+
+ <url-configs>
+ <url-config>
+ <description>sample: remove jsessionid</description>
+ <url-pattern>^(.*);jsessionid=(.*)jvm[1-9](.*)$</url-pattern>
+ <seo>
+ <replacement>$1$3</replacement>
+ </seo>
+ <forward>
+ <replacement>$1$3</replacement>
+ <responsecode>301</responsecode>
+ </forward>
+ </url-config>
+
+ <url-config>
+ <description>sample: remove /ecommerce/main</description>
+ <url-pattern>^/ecommerce/main$</url-pattern>
+ <seo>
+ <replacement>/ecommerce/</replacement>
+ </seo>
+ <forward>
+ <replacement>/ecommerce/</replacement>
+ <responsecode>301</responsecode>
+ </forward>
+ </url-config>
+
+ <url-config>
+ <description>sample: remove /main</description>
+ <url-pattern>^/main$</url-pattern>
+ <seo>
+ <replacement>/</replacement>
+ </seo>
+ <forward>
+ <replacement>/</replacement>
+ <responsecode>301</responsecode>
+ </forward>
+ </url-config>
+ </url-configs>
+
+ <char-filters>
+ <char-filter>
+ <character-pattern>\u00fc</character-pattern>
+ <replacement>ue</replacement>
+ </char-filter>
+ <char-filter>
+ <character-pattern>\u00e4</character-pattern>
+ <replacement>ae</replacement>
+ </char-filter>
+ <char-filter>
+ <character-pattern>\u00f6</character-pattern>
+ <replacement>oe</replacement>
+ </char-filter>
+ <char-filter>
+ <character-pattern>\u00df</character-pattern>
+ <replacement>ss</replacement>
+ </char-filter>
+ <char-filter>
+ <character-pattern>\\+</character-pattern>
+ <replacement>und</replacement>
+ </char-filter>
+ <char-filter>
+ <character-pattern>\u0026</character-pattern>
+ <replacement>und</replacement>
+ </char-filter>
+ <char-filter>
+ <character-pattern>è</character-pattern>
+ <replacement>e</replacement>
+ </char-filter>
+ <!-- please keep the following 2 filters, don't remove them -->
+ <char-filter>
+ <character-pattern>[^A-Za-z0-9+-]</character-pattern>
+ <replacement>-</replacement>
+ </char-filter>
+ <char-filter>
+ <character-pattern>-{2,}</character-pattern>
+ <replacement>-</replacement>
+ </char-filter>
+ </char-filters>
+</seo-config>
diff --git a/applications/product/config/freemarkerTransforms.properties b/applications/product/config/freemarkerTransforms.properties
index 98d314c..450c52d 100644
--- a/applications/product/config/freemarkerTransforms.properties
+++ b/applications/product/config/freemarkerTransforms.properties
@@ -21,6 +21,8 @@
# entries are in the form: key=transform name, property=transform class name
-ofbizCatalogAltUrl=org.ofbiz.product.category.OfbizCatalogAltUrlTransform
-ofbizCatalogUrl=org.ofbiz.product.category.CatalogUrlDirective
+#ofbizCatalogAltUrl=org.ofbiz.product.category.ftl.OfbizCatalogAltUrlTransform
+#ofbizCatalogUrl=org.ofbiz.product.category.ftl.CatalogUrlDirective
+ofbizCatalogAltUrl=org.ofbiz.product.category.ftl.CatalogAltUrlSeoTransform
+ofbizCatalogUrl=org.ofbiz.product.category.ftl.CatalogUrlSeoTransform
diff --git a/applications/product/dtd/SeoConfig.xsd b/applications/product/dtd/SeoConfig.xsd
new file mode 100644
index 0000000..194e830
--- /dev/null
+++ b/applications/product/dtd/SeoConfig.xsd
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+ <xs:element name="seo-config">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="1" ref="regexpifmatch"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="category-url"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="jsessionid"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="url-configs"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="char-filters"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="regexpifmatch"/>
+ <xs:element name="category-url">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="1" ref="value"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="allowed-context-paths"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="category-name"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="category-url-suffix"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="value" default="disable"/>
+ <xs:element name="allowed-context-paths"/>
+ <xs:element name="category-name" default="disable"/>
+ <xs:element name="category-url-suffix" default=".html"/>
+ <xs:element name="jsessionid">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="1" ref="anonymous"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="user"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="anonymous">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="1" ref="value"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="user">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="1" ref="value"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="exceptions"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="exceptions">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="url-pattern"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="url-configs">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="url-config"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="char-filters">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="char-filter"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="url-config">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="1" ref="description"/>
+ <xs:element minOccurs="1" maxOccurs="1" ref="url-pattern"/>
+ <xs:element minOccurs="1" maxOccurs="1" ref="seo"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="forward"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="description"/>
+ <xs:element name="url-pattern"/>
+ <xs:element name="seo">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="1" ref="replacement"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="forward">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="1" ref="replacement"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="responsecode"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="replacement"/>
+ <xs:element name="responsecode" default="301"/>
+ <xs:element name="char-filter">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="1" ref="character-pattern"/>
+ <xs:element minOccurs="1" maxOccurs="1" ref="replacement"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="character-pattern"/>
+</xs:schema>
diff --git a/applications/product/src/org/ofbiz/product/category/CatalogUrlSeoFilter.java b/applications/product/src/org/ofbiz/product/category/CatalogUrlSeoFilter.java
new file mode 100644
index 0000000..a292c55
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/CatalogUrlSeoFilter.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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 javax.servlet.FilterChain;
+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 org.ofbiz.base.util.UtilValidate;
+import org.ofbiz.common.UrlServletHelper;
+import org.ofbiz.entity.Delegator;
+import org.ofbiz.product.category.ftl.CatalogUrlSeoTransform;
+
+public class CatalogUrlSeoFilter extends CatalogUrlFilter {
+
+ public final static String module = CatalogUrlSeoFilter.class.getName();
+
+ protected static String defaultLocaleString = null;
+ protected static String redirectUrl = null;
+
+ /**
+ * @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;
+ Delegator delegator = (Delegator) httpRequest.getSession().getServletContext().getAttribute("delegator");
+
+ // Get ServletContext
+ ServletContext servletContext = config.getServletContext();
+
+ // Set request attribute and session
+ UrlServletHelper.setRequestAttributes(request, delegator, servletContext);
+
+ // set initial parameters
+ String initDefaultLocalesString = config.getInitParameter("defaultLocaleString");
+ String initRedirectUrl = config.getInitParameter("redirectUrl");
+ defaultLocaleString = UtilValidate.isNotEmpty(initDefaultLocalesString) ? initDefaultLocalesString : "";
+ redirectUrl = UtilValidate.isNotEmpty(initRedirectUrl) ? initRedirectUrl : "";
+
+ // set the ServletContext in the request for future use
+ httpRequest.setAttribute("servletContext", config.getServletContext());
+ if (CatalogUrlSeoTransform.forwardUri(httpRequest, httpResponse, delegator, ControlServlet.controlServlet)) {
+ return;
+ }
+ super.doFilter(httpRequest, httpResponse, chain);
+ }
+
+}
diff --git a/applications/product/src/org/ofbiz/product/category/CategoryServices.java b/applications/product/src/org/ofbiz/product/category/CategoryServices.java
index 2a81c99..bceec73 100644
--- a/applications/product/src/org/ofbiz/product/category/CategoryServices.java
+++ b/applications/product/src/org/ofbiz/product/category/CategoryServices.java
@@ -524,7 +524,7 @@
}
}
} catch (GenericEntityException e) {
- e.printStackTrace();
+ Debug.logWarning(e, module);
return "error";
}
return "success";
diff --git a/applications/product/src/org/ofbiz/product/category/ControlServlet.java b/applications/product/src/org/ofbiz/product/category/ControlServlet.java
new file mode 100644
index 0000000..efc21c5
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/ControlServlet.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * ControlServlet.java - Master servlet for the web application.
+ */
+@SuppressWarnings("serial")
+public class ControlServlet extends org.ofbiz.webapp.control.ControlServlet {
+
+ public static final String module = ControlServlet.class.getName();
+
+ protected static String defaultPage = null;
+ protected static String pageNotFound = null;
+ protected static String controlServlet = null;
+
+ public ControlServlet() {
+ 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 (UtilValidate.isEmpty(pageNotFound)) {
+ pageNotFound = context.getInitParameter("pageNotFound");
+ }
+ if (UtilValidate.isEmpty(pageNotFound)) {
+ pageNotFound = "/pagenotfound";
+ }
+
+ if (defaultPage.startsWith("/") && defaultPage.lastIndexOf("/") > 0) {
+ controlServlet = defaultPage.substring(1);
+ controlServlet = controlServlet.substring(0, controlServlet.indexOf("/"));
+ }
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/SeoCatalogUrlServlet.java b/applications/product/src/org/ofbiz/product/category/SeoCatalogUrlServlet.java
new file mode 100644
index 0000000..3768186
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/SeoCatalogUrlServlet.java
@@ -0,0 +1,206 @@
+/*******************************************************************************
+ * 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 javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+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.UtilMisc;
+import org.ofbiz.base.util.UtilValidate;
+import org.ofbiz.entity.Delegator;
+import org.ofbiz.entity.GenericEntityException;
+
+/**
+ * SeoCatalogUrlServlet.java
+ */
+@SuppressWarnings("serial")
+public class SeoCatalogUrlServlet extends HttpServlet {
+
+ public static final String module = SeoCatalogUrlServlet.class.getName();
+ public static final String CATALOG_URL_MOUNT_POINT = "products";
+ public static final String PRODUCT_REQUEST = "product";
+ public static final String CATEGORY_REQUEST = "category";
+
+ public SeoCatalogUrlServlet() {
+ super();
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServlet#init(javax.servlet.ServletConfig)
+ */
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ doGet(request, response);
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ Delegator delegator = (Delegator) getServletContext().getAttribute("delegator");
+
+ String pathInfo = request.getPathInfo();
+ List<String> pathElements = StringUtil.split(pathInfo, "/");
+
+ // look for productId
+ String productId = null;
+ try {
+ String lastPathElement = pathElements.get(pathElements.size() - 1);
+ if (lastPathElement.startsWith("p_") || delegator.findOne("Product", UtilMisc.toMap("productId", lastPathElement), true) != null) {
+ if (lastPathElement.startsWith("p_")) {
+ productId = lastPathElement.substring(2);
+ } else {
+ productId = lastPathElement;
+ }
+ pathElements.remove(pathElements.size() - 1);
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error looking up product info for ProductUrl with path info [" + pathInfo + "]: " + e.toString(), module);
+ }
+
+ // get category info going with the IDs that remain
+ String categoryId = null;
+ if (pathElements.size() == 1) {
+ CategoryWorker.setTrail(request, pathElements.get(0), null);
+ categoryId = pathElements.get(0);
+ } else if (pathElements.size() == 2) {
+ CategoryWorker.setTrail(request, pathElements.get(1), pathElements.get(0));
+ categoryId = pathElements.get(1);
+ } else if (pathElements.size() > 2) {
+ List<String> trail = CategoryWorker.getTrail(request);
+ if (trail == null) {
+ trail = FastList.newInstance();
+ }
+
+ if (trail.contains(pathElements.get(0))) {
+ // first category is in the trail, so remove it everything after that and fill it in with the list from the pathInfo
+ int firstElementIndex = trail.indexOf(pathElements.get(0));
+ while (trail.size() > firstElementIndex) {
+ trail.remove(firstElementIndex);
+ }
+ trail.addAll(pathElements);
+ } else {
+ // first category is NOT in the trail, so clear out the trail and use the pathElements list
+ trail.clear();
+ trail.addAll(pathElements);
+ }
+ CategoryWorker.setTrail(request, trail);
+ categoryId = pathElements.get(pathElements.size() - 1);
+ }
+ if (categoryId != null) {
+ request.setAttribute("productCategoryId", categoryId);
+ }
+
+ String rootCategoryId = null;
+ if (pathElements.size() >= 1) {
+ rootCategoryId = pathElements.get(0);
+ }
+ if (rootCategoryId != null) {
+ request.setAttribute("rootCategoryId", rootCategoryId);
+ }
+
+ if (productId != null) {
+ request.setAttribute("product_id", productId);
+ request.setAttribute("productId", productId);
+ }
+
+ RequestDispatcher rd = request.getRequestDispatcher("/" + (UtilValidate.isEmpty(SeoControlServlet.controlServlet) ? "" : (SeoControlServlet.controlServlet + "/"))
+ + (productId != null ? PRODUCT_REQUEST : CATEGORY_REQUEST));
+ rd.forward(request, response);
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServlet#destroy()
+ */
+ @Override
+ public void destroy() {
+ super.destroy();
+ }
+
+ public static String makeCatalogUrl(HttpServletRequest request, String productId, String currentCategoryId, String previousCategoryId) {
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(request.getSession().getServletContext().getContextPath());
+ if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') {
+ urlBuilder.append("/");
+ }
+ urlBuilder.append(CATALOG_URL_MOUNT_POINT);
+
+ if (UtilValidate.isNotEmpty(currentCategoryId)) {
+ List<String> trail = CategoryWorker.getTrail(request);
+ trail = CategoryWorker.adjustTrail(trail, currentCategoryId, previousCategoryId);
+ for (String trailCategoryId : trail) {
+ if ("TOP".equals(trailCategoryId)) continue;
+ urlBuilder.append("/");
+ urlBuilder.append(trailCategoryId);
+ }
+ }
+
+ if (UtilValidate.isNotEmpty(productId)) {
+ urlBuilder.append("/p_");
+ urlBuilder.append(productId);
+ }
+
+ return urlBuilder.toString();
+ }
+
+ public static String makeCatalogUrl(String contextPath, List<String> crumb, String productId, String currentCategoryId, String previousCategoryId) {
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(contextPath);
+ if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') {
+ urlBuilder.append("/");
+ }
+ urlBuilder.append(CATALOG_URL_MOUNT_POINT);
+
+ if (UtilValidate.isNotEmpty(currentCategoryId)) {
+ crumb = CategoryWorker.adjustTrail(crumb, currentCategoryId, previousCategoryId);
+ for (String trailCategoryId : crumb) {
+ if ("TOP".equals(trailCategoryId)) continue;
+ urlBuilder.append("/");
+ urlBuilder.append(trailCategoryId);
+ }
+ }
+
+ if (UtilValidate.isNotEmpty(productId)) {
+ urlBuilder.append("/p_");
+ urlBuilder.append(productId);
+ }
+
+ return urlBuilder.toString();
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java b/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java
new file mode 100644
index 0000000..fdbc46f
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/SeoConfigUtil.java
@@ -0,0 +1,537 @@
+/*******************************************************************************
+ * 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);
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java b/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java
new file mode 100644
index 0000000..f8ae6c2
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/SeoContentUrlFilter.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * 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;
+ String pathInfo = UtilHttp.getFullRequestUrl(httpRequest);
+ 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();
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java b/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java
new file mode 100644
index 0000000..0530eea
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/SeoContextFilter.java
@@ -0,0 +1,395 @@
+/*******************************************************************************
+ * 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.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javolution.util.FastSet;
+
+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();
+
+ protected Set<String> WebServlets = FastSet.newInstance();
+
+ public void init(FilterConfig config) throws ServletException {
+ super.init(config);
+
+ Map<String, ? extends ServletRegistration> servletRegistrations = config.getServletContext().getServletRegistrations();
+ for (String key : servletRegistrations.keySet()) {
+ Collection<String> servlets = servletRegistrations.get(key).getMappings();
+ for (String servlet : servlets) {
+ if (servlet.endsWith("/*")) {
+ servlet = servlet.substring(0, servlet.length() - 2);
+ if (UtilValidate.isNotEmpty(servlet) && !WebServlets.contains(servlet)) {
+ WebServlets.add(servlet);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @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
+
+ // 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
+ String serverRootUrl = UtilHttp.getServerRootUrl(httpRequest);
+ httpRequest.setAttribute("_SERVER_ROOT_URL_", serverRootUrl);
+
+ // 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, ":");
+
+ 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);
+ }
+
+ String requestUri = UtilHttp.getRequestUriFromTarget(httpRequest.getRequestURI());
+
+ // Verbose Debugging
+ if (Debug.verboseOn()) {
+ for (String allow : allowList) {
+ Debug.logVerbose("[Allow]: " + allow, module);
+ }
+ Debug.logVerbose("[View Name]: " + viewName, module);
+ Debug.logVerbose("[Request Uri]: " + requestUri, 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()) && !allowList.contains(requestUri) && !allowList.contains("/" + viewName)),
+ 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())
+ && !allowList.contains(requestUri) && !allowList.contains("/" + viewName)
+ && (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;
+ } else if ((allowList.contains(requestPath) || allowList.contains(requestInfo) || allowList.contains(httpRequest.getServletPath())
+ || allowList.contains(requestUri) || allowList.contains("/" + viewName))
+ && !WebServlets.contains(httpRequest.getServletPath())) {
+ request.setAttribute(SeoControlServlet.REQUEST_IN_ALLOW_LIST, Boolean.TRUE);
+ }
+ }
+
+ // 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.getSeoPatterns() != null && SeoConfigUtil.getForwardReplacements() != null) {
+ Iterator<String> keys = SeoConfigUtil.getSeoPatterns().keySet().iterator();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Pattern pattern = SeoConfigUtil.getSeoPatterns().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 {
+ Debug.logInfo("Can NOT forward this url: " + uri, module);
+ }
+ return foundMatch;
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java b/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java
new file mode 100644
index 0000000..817ed1a
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/SeoControlServlet.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * 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.net.URLEncoder;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.jasper.servlet.JspServlet;
+import org.ofbiz.base.util.Debug;
+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 static final String REQUEST_IN_ALLOW_LIST = "_REQUEST_IN_ALLOW_LIST_";
+
+ 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();
+ }
+
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String uri = URLEncoder.encode(request.getRequestURI(), "UTF-8");
+ if (request.getAttribute(REQUEST_IN_ALLOW_LIST) != null || request.getAttribute("_jsp_" + uri) != null) {
+ if (request.getRequestURI().toLowerCase().endsWith(".jsp") || request.getRequestURI().toLowerCase().endsWith(".jspx") ) {
+ JspServlet jspServlet = new JspServlet();
+ jspServlet.init(this.getServletConfig());
+ jspServlet.service(request, response);
+ } else {
+ DefaultServlet defaultServlet = new DefaultServlet();
+ defaultServlet.init(this.getServletConfig());
+ defaultServlet.service(request, response);
+ }
+ return;
+ }
+ super.doGet(request, response);
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java b/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java
new file mode 100644
index 0000000..66540dc
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/SeoUrlUtil.java
@@ -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.getCharFilters().keySet()) {
+ url = url.replaceAll(characterPattern, SeoConfigUtil.getCharFilters().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;
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java b/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java
new file mode 100644
index 0000000..ed21da3
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java
@@ -0,0 +1,322 @@
+/*******************************************************************************
+ * 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.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.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.GeneralException;
+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.product.category.ftl.UrlRegexpTransform;
+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;
+
+/**
+ * UrlRegexpContextFilter - Restricts access to raw files and configures servlet objects.
+ */
+public class UrlRegexpContextFilter extends ContextFilter {
+
+ public static final String module = UrlRegexpContextFilter.class.getName();
+
+ /**
+ * @throws GeneralException
+ * @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 = UrlRegexpTransform.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
+
+
+ // 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", config.getServletContext().getAttribute("webSiteId"));
+ }
+
+ // set the filesystem path of context root.
+ httpRequest.setAttribute("_CONTEXT_ROOT_", config.getServletContext().getRealPath("/"));
+
+ // set the server root url
+ String serverRootUrl = UtilHttp.getServerRootUrl(httpRequest);
+ httpRequest.setAttribute("_SERVER_ROOT_URL_", serverRootUrl);
+
+ // 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) {
+ if (debug) Debug.log("[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);
+ }
+
+ // 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.
+ }
+
+ // 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("[View name]: " + viewName, 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) {
+ 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);
+ } 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("authorization", null);
+ config.getServletContext().setAttribute("security", null);
+ config.getServletContext().setAttribute("dispatcher", null);
+
+ // initialize authorizer
+ // getAuthz();
+ // initialize security
+ Security security = getSecurity();
+ // initialize the services dispatcher
+ LocalDispatcher dispatcher = getDispatcher(config.getServletContext());
+
+ // set web context objects
+ httpRequest.getSession().setAttribute("dispatcher", dispatcher);
+ httpRequest.getSession().setAttribute("security", security);
+
+ httpRequest.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);
+
+ // reset thread local security
+ // AbstractAuthorization.clearThreadLocal();
+ }
+
+}
diff --git a/applications/product/src/org/ofbiz/product/category/OfbizCatalogAltUrlTransform.java b/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
similarity index 60%
copy from applications/product/src/org/ofbiz/product/category/OfbizCatalogAltUrlTransform.java
copy to applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
index f8f6373..0aebcdb 100644
--- a/applications/product/src/org/ofbiz/product/category/OfbizCatalogAltUrlTransform.java
+++ b/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
-package org.ofbiz.product.category;
+package org.ofbiz.product.category.ftl;
import java.io.IOException;
import java.io.Writer;
@@ -25,13 +25,16 @@
import javax.servlet.http.HttpServletRequest;
+import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.template.FreeMarkerWorker;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
-import org.ofbiz.entity.util.EntityQuery;
+import org.ofbiz.product.category.CatalogUrlFilter;
+import org.ofbiz.product.category.CategoryContentWrapper;
+import org.ofbiz.product.category.SeoConfigUtil;
import org.ofbiz.product.product.ProductContentWrapper;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.webapp.OfbizUrlBuilder;
@@ -46,10 +49,9 @@
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateTransformModel;
-public class OfbizCatalogAltUrlTransform implements TemplateTransformModel {
- public final static String module = OfbizCatalogUrlTransform.class.getName();
+public class CatalogAltUrlSeoTransform implements TemplateTransformModel {
+ public final static String module = CatalogUrlSeoTransform.class.getName();
- @SuppressWarnings("unchecked")
public String getStringArg(Map args, String key) {
Object o = args.get(key);
if (o instanceof SimpleScalar) {
@@ -78,26 +80,21 @@
}
@Override
- @SuppressWarnings("unchecked")
- public Writer getWriter(final Writer out, final Map args)
- throws TemplateModelException, IOException {
+ public Writer getWriter(final Writer out, final Map args) throws TemplateModelException, IOException {
final StringBuilder buf = new StringBuilder();
final boolean fullPath = checkArg(args, "fullPath", false);
final boolean secure = checkArg(args, "secure", false);
return new Writer(out) {
-
- @Override
+
public void write(char[] cbuf, int off, int len) throws IOException {
buf.append(cbuf, off, len);
}
-
- @Override
+
public void flush() throws IOException {
out.flush();
}
-
- @Override
+
public void close() throws IOException {
try {
Environment env = Environment.getCurrentEnvironment();
@@ -106,7 +103,7 @@
String productCategoryId = getStringArg(args, "productCategoryId");
String productId = getStringArg(args, "productId");
String url = "";
-
+
Object prefix = env.getVariable("urlPrefix");
String viewSize = getStringArg(args, "viewSize");
String viewIndex = getStringArg(args, "viewIndex");
@@ -116,14 +113,26 @@
HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
StringBuilder newURL = new StringBuilder();
if (UtilValidate.isNotEmpty(productId)) {
- url = CatalogUrlFilter.makeProductUrl(request, previousCategoryId, productCategoryId, productId);
+ if (SeoConfigUtil.isCategoryUrlEnabled(request.getContextPath())) {
+ url = CatalogUrlSeoTransform.makeProductUrl(request, productId, productCategoryId, previousCategoryId);
+ } else {
+ url = CatalogUrlFilter.makeProductUrl(request, previousCategoryId, productCategoryId, productId);
+ }
} else {
- url = CatalogUrlFilter.makeCategoryUrl(request, previousCategoryId, productCategoryId, productId, viewSize, viewIndex, viewSort, searchString);
+ if (SeoConfigUtil.isCategoryUrlEnabled(request.getContextPath())) {
+ url = CatalogUrlSeoTransform.makeCategoryUrl(request, productCategoryId, previousCategoryId, viewSize, viewIndex, viewSort, searchString);
+ } else {
+ url = CatalogUrlFilter.makeCategoryUrl(request, previousCategoryId, productCategoryId, productId, viewSize, viewIndex, viewSort, searchString);
+ }
}
// make the link
- if (fullPath){
- OfbizUrlBuilder builder = OfbizUrlBuilder.from(request);
- builder.buildHostPart(newURL, url, secure);
+ if (fullPath) {
+ try {
+ OfbizUrlBuilder builder = OfbizUrlBuilder.from(request);
+ builder.buildHostPart(newURL, "", secure);
+ } catch (WebAppConfigurationException e) {
+ Debug.logError(e.getMessage(), module);
+ }
}
newURL.append(url);
out.write(newURL.toString());
@@ -131,14 +140,31 @@
Delegator delegator = FreeMarkerWorker.getWrappedObject("delegator", env);
LocalDispatcher dispatcher = FreeMarkerWorker.getWrappedObject("dispatcher", env);
Locale locale = (Locale) args.get("locale");
+ String prefixString = ((StringModel) prefix).getAsString();
+ prefixString = prefixString.replaceAll("/", "/");
+ String contextPath = prefixString;
+ int lastSlashIndex = prefixString.lastIndexOf("/");
+ if (lastSlashIndex > -1 && lastSlashIndex < prefixString.length()) {
+ contextPath = prefixString.substring(prefixString.lastIndexOf("/"));
+ }
if (UtilValidate.isNotEmpty(productId)) {
- GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne();
+ GenericValue product = delegator.findOne("Product", UtilMisc.toMap("productId", productId), false);
ProductContentWrapper wrapper = new ProductContentWrapper(dispatcher, product, locale, "text/html");
- url = CatalogUrlFilter.makeProductUrl(delegator, wrapper, null, ((StringModel) prefix).getAsString(), previousCategoryId, productCategoryId, productId);
+ if (SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ url = CatalogUrlSeoTransform.makeProductUrl(delegator, wrapper, prefixString, contextPath, productCategoryId, previousCategoryId, productId);
+ } else {
+ url = CatalogUrlFilter.makeProductUrl(delegator, wrapper, null, prefixString, previousCategoryId, productCategoryId,
+ productId);
+ }
} else {
- GenericValue productCategory = EntityQuery.use(delegator).from("ProductCategory").where("productCategoryId", productCategoryId).queryOne();
+ GenericValue productCategory = delegator.findOne("ProductCategory", UtilMisc.toMap("productCategoryId", productCategoryId), false);
CategoryContentWrapper wrapper = new CategoryContentWrapper(dispatcher, productCategory, locale, "text/html");
- url = CatalogUrlFilter.makeCategoryUrl(delegator, wrapper, null, ((StringModel) prefix).getAsString(), previousCategoryId, productCategoryId, productId, viewSize, viewIndex, viewSort, searchString);
+ if (SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ url = CatalogUrlSeoTransform.makeCategoryUrl(delegator, wrapper, prefixString, productCategoryId, previousCategoryId, productId, viewSize, viewIndex, viewSort, searchString);
+ } else {
+ url = CatalogUrlFilter.makeCategoryUrl(delegator, wrapper, null, prefixString, previousCategoryId, productCategoryId,
+ productId, viewSize, viewIndex, viewSort, searchString);
+ }
}
out.write(url.toString());
} else {
@@ -148,8 +174,6 @@
throw new IOException(e.getMessage());
} catch (GenericEntityException e) {
throw new IOException(e.getMessage());
- } catch (WebAppConfigurationException e) {
- throw new IOException(e.getMessage());
}
}
};
diff --git a/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java b/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java
new file mode 100644
index 0000000..ed9c3b0
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java
@@ -0,0 +1,840 @@
+/*******************************************************************************
+ * 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.ftl;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.apache.oro.text.regex.Perl5Matcher;
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.StringUtil;
+import org.ofbiz.base.util.StringUtil.StringWrapper;
+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.GenericEntityException;
+import org.ofbiz.entity.GenericValue;
+import org.ofbiz.entity.condition.EntityCondition;
+import org.ofbiz.entity.condition.EntityExpr;
+import org.ofbiz.entity.condition.EntityOperator;
+import org.ofbiz.product.category.CatalogUrlServlet;
+import org.ofbiz.product.category.CategoryContentWrapper;
+import org.ofbiz.product.category.CategoryWorker;
+import org.ofbiz.product.category.SeoConfigUtil;
+import org.ofbiz.product.category.SeoUrlUtil;
+import org.ofbiz.product.product.ProductContentWrapper;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.BeanModel;
+import freemarker.ext.beans.StringModel;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+
+public class CatalogUrlSeoTransform implements TemplateTransformModel {
+ public final static String module = CatalogUrlSeoTransform.class.getName();
+
+ private static Map<String, String> categoryNameIdMap = null;
+ private static Map<String, String> categoryIdNameMap = null;
+ private static boolean categoryMapInitialed = false;
+ private static final String asciiRegexp = "^[0-9-_a-zA-Z]*$";
+ private static Pattern asciiPattern = null;
+ public static final String URL_HYPHEN = "-";
+
+ static {
+ if (!SeoConfigUtil.isInitialed()) {
+ SeoConfigUtil.init();
+ }
+ try {
+ Perl5Compiler perlCompiler = new Perl5Compiler();
+ asciiPattern = perlCompiler.compile(asciiRegexp, Perl5Compiler.READ_ONLY_MASK);
+ } catch (MalformedPatternException e1) {
+ Debug.logWarning(e1, module);
+ }
+ }
+
+ public String getStringArg(Map args, String key) {
+ Object o = args.get(key);
+ if (o instanceof SimpleScalar) {
+ return ((SimpleScalar) o).getAsString();
+ } else if (o instanceof StringModel) {
+ return ((StringModel) o).getAsString();
+ }
+ return null;
+ }
+
+ @Override
+ public Writer getWriter(final Writer out, final Map args)
+ throws TemplateModelException, IOException {
+ final StringBuilder buf = new StringBuilder();
+
+ return new Writer(out) {
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ buf.append(cbuf, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ Environment env = Environment.getCurrentEnvironment();
+ BeanModel req = (BeanModel) env.getVariable("request");
+ if (req != null) {
+ String productId = getStringArg(args, "productId");
+ String currentCategoryId = getStringArg(args, "currentCategoryId");
+ String previousCategoryId = getStringArg(args, "previousCategoryId");
+ HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
+
+ if (!isCategoryMapInitialed()) {
+ initCategoryMap(request);
+ }
+
+ String catalogUrl = "";
+ if (SeoConfigUtil.isCategoryUrlEnabled(request.getContextPath())) {
+ if (UtilValidate.isEmpty(productId)) {
+ catalogUrl = makeCategoryUrl(request, currentCategoryId, previousCategoryId, null, null, null, null);
+ } else {
+ catalogUrl = makeProductUrl(request, productId, currentCategoryId, previousCategoryId);
+ }
+ } else {
+ catalogUrl = CatalogUrlServlet.makeCatalogUrl(request, productId, currentCategoryId, previousCategoryId);
+ }
+ out.write(catalogUrl);
+ }
+ } catch (TemplateModelException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+ };
+ }
+
+ /**
+ * Check whether the category map is initialed.
+ *
+ * @return a boolean value to indicate whether the category map has been initialized.
+ */
+ public static boolean isCategoryMapInitialed() {
+ return categoryMapInitialed;
+ }
+
+ /**
+ * Get the category name/id map.
+ *
+ * @return the category name/id map
+ */
+ public static Map<String, String> getCategoryNameIdMap() {
+ return categoryNameIdMap;
+ }
+
+ /**
+ * Get the category id/name map.
+ *
+ * @return the category id/name map
+ */
+ public static Map<String, String> getCategoryIdNameMap() {
+ return categoryIdNameMap;
+ }
+
+ /**
+ * Initial category-name/category-id map.
+ * Note: as a key, the category-name should be:
+ * 1. ascii
+ * 2. lower cased and use hyphen between the words.
+ * If not, the category id will be used.
+ *
+ */
+ public static synchronized void initCategoryMap(HttpServletRequest request) {
+ Delegator delegator = (Delegator) request.getAttribute("delegator");
+ initCategoryMap(request, delegator);
+ }
+
+ public static synchronized void initCategoryMap(HttpServletRequest request, Delegator delegator) {
+ if (SeoConfigUtil.checkCategoryUrl()) {
+ categoryNameIdMap = FastMap.newInstance();
+ categoryIdNameMap = FastMap.newInstance();
+ Perl5Matcher matcher = new Perl5Matcher();
+
+ try {
+ Collection<GenericValue> allCategories = delegator.findList("ProductCategory", null, UtilMisc.toSet("productCategoryId", "categoryName"), null, null, false);
+ for (GenericValue category : allCategories) {
+ String categoryName = category.getString("categoryName");
+ String categoryNameId = null;
+ String categoryIdName = null;
+ String categoryId = category.getString("productCategoryId");
+ if (UtilValidate.isNotEmpty(categoryName)) {
+ categoryName = SeoUrlUtil.replaceSpecialCharsUrl(categoryName.trim());
+ if (matcher.matches(categoryName, asciiPattern)) {
+ categoryIdName = categoryName.replaceAll(" ", URL_HYPHEN);
+ categoryNameId = categoryIdName + URL_HYPHEN + categoryId.trim().replaceAll(" ", URL_HYPHEN);
+ } else {
+ categoryIdName = categoryId.trim().replaceAll(" ", URL_HYPHEN);
+ categoryNameId = categoryIdName;
+ }
+ } else {
+ GenericValue productCategory = delegator.findOne("ProductCategory", UtilMisc.toMap("productCategoryId", categoryId), true);
+ CategoryContentWrapper wrapper = new CategoryContentWrapper(productCategory, request);
+ StringWrapper alternativeUrl = wrapper.get("ALTERNATIVE_URL");
+ if (UtilValidate.isNotEmpty(alternativeUrl) && UtilValidate.isNotEmpty(alternativeUrl.toString())) {
+ categoryIdName = SeoUrlUtil.replaceSpecialCharsUrl(alternativeUrl.toString());
+ categoryNameId = categoryIdName + URL_HYPHEN + categoryId.trim().replaceAll(" ", URL_HYPHEN);
+ } else {
+ categoryNameId = categoryId.trim().replaceAll(" ", URL_HYPHEN);
+ categoryIdName = categoryNameId;
+ }
+ }
+ if (categoryNameIdMap.containsKey(categoryNameId)) {
+ categoryNameId = categoryId.trim().replaceAll(" ", URL_HYPHEN);
+ categoryIdName = categoryNameId;
+ }
+ if (!matcher.matches(categoryNameId, asciiPattern) || categoryNameIdMap.containsKey(categoryNameId)) {
+ continue;
+ }
+ categoryNameIdMap.put(categoryNameId, categoryId);
+ categoryIdNameMap.put(categoryId, categoryIdName);
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+ }
+ categoryMapInitialed = true;
+ }
+
+ /**
+ * Make product url according to the configurations.
+ *
+ * @return String a catalog url
+ */
+ public static String makeProductUrl(HttpServletRequest request, String productId, String currentCategoryId, String previousCategoryId) {
+ Delegator delegator = (Delegator) request.getAttribute("delegator");
+ if (!isCategoryMapInitialed()) {
+ initCategoryMap(request);
+ }
+
+ String contextPath = request.getContextPath();
+ StringBuilder urlBuilder = new StringBuilder();
+ GenericValue product = null;
+ urlBuilder.append((request.getSession().getServletContext()).getContextPath());
+ if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') {
+ urlBuilder.append("/");
+ }
+ if (UtilValidate.isNotEmpty(productId)) {
+ try {
+ product = delegator.findOne("Product", UtilMisc.toMap("productId", productId), true);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error looking up product info for productId [" + productId + "]: " + e.toString(), module);
+ }
+ }
+ if (product != null) {
+ urlBuilder.append(CatalogUrlServlet.PRODUCT_REQUEST + "/");
+ }
+
+ if (UtilValidate.isNotEmpty(currentCategoryId)) {
+ List<String> trail = CategoryWorker.getTrail(request);
+ trail = CategoryWorker.adjustTrail(trail, currentCategoryId, previousCategoryId);
+ if (!SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ for (String trailCategoryId: trail) {
+ if ("TOP".equals(trailCategoryId)) continue;
+ urlBuilder.append("/");
+ urlBuilder.append(trailCategoryId);
+ }
+ } else {
+ if (trail.size() > 1) {
+ String lastCategoryId = trail.get(trail.size() - 1);
+ if (!"TOP".equals(lastCategoryId)) {
+ if (SeoConfigUtil.isCategoryNameEnabled()) {
+ String categoryName = CatalogUrlSeoTransform.getCategoryIdNameMap().get(lastCategoryId);
+ if (UtilValidate.isNotEmpty(categoryName)) {
+ urlBuilder.append(categoryName);
+ if (product != null) {
+ urlBuilder.append(URL_HYPHEN);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (UtilValidate.isNotEmpty(productId)) {
+ if (product != null) {
+ String productName = product.getString("productName");
+ productName = SeoUrlUtil.replaceSpecialCharsUrl(productName);
+ if (UtilValidate.isNotEmpty(productName)) {
+ urlBuilder.append(productName + URL_HYPHEN);
+ } else {
+ ProductContentWrapper wrapper = new ProductContentWrapper(product, request);
+ StringWrapper alternativeUrl = wrapper.get("ALTERNATIVE_URL");
+ if (UtilValidate.isNotEmpty(alternativeUrl) && UtilValidate.isNotEmpty(alternativeUrl.toString())) {
+ productName = SeoUrlUtil.replaceSpecialCharsUrl(alternativeUrl.toString());
+ if (UtilValidate.isNotEmpty(productName)) {
+ urlBuilder.append(productName + URL_HYPHEN);
+ }
+ }
+ }
+ }
+ try {
+ //SeoConfigUtil.addSpecialProductId(productId);
+ urlBuilder.append(productId);
+ } catch (Exception e) {
+ urlBuilder.append(productId);
+ }
+ }
+
+ if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(SeoConfigUtil.getCategoryUrlSuffix())) {
+ urlBuilder.append(SeoConfigUtil.getCategoryUrlSuffix());
+ }
+
+ return urlBuilder.toString();
+ }
+
+ /**
+ * Make category url according to the configurations.
+ *
+ * @return String a category url
+ */
+ public static String makeCategoryUrl(HttpServletRequest request, String currentCategoryId, String previousCategoryId, String viewSize, String viewIndex, String viewSort, String searchString) {
+
+ if (!isCategoryMapInitialed()) {
+ initCategoryMap(request);
+ }
+
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append((request.getSession().getServletContext()).getContextPath());
+ if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') {
+ urlBuilder.append("/");
+ }
+ urlBuilder.append(CatalogUrlServlet.CATEGORY_REQUEST + "/");
+
+ if (UtilValidate.isNotEmpty(currentCategoryId)) {
+ List<String> trail = CategoryWorker.getTrail(request);
+ trail = CategoryWorker.adjustTrail(trail, currentCategoryId, previousCategoryId);
+ if (trail.size() > 1) {
+ String lastCategoryId = trail.get(trail.size() - 1);
+ if (!"TOP".equals(lastCategoryId)) {
+ String categoryName = CatalogUrlSeoTransform.getCategoryIdNameMap().get(lastCategoryId);
+ if (UtilValidate.isNotEmpty(categoryName)) {
+ urlBuilder.append(categoryName);
+ urlBuilder.append(URL_HYPHEN);
+ urlBuilder.append(lastCategoryId.trim().replaceAll(" ", URL_HYPHEN));
+ } else {
+ urlBuilder.append(lastCategoryId.trim().replaceAll(" ", URL_HYPHEN));
+ }
+ }
+ }
+ }
+
+ if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(SeoConfigUtil.getCategoryUrlSuffix())) {
+ urlBuilder.append(SeoConfigUtil.getCategoryUrlSuffix());
+ }
+
+ // append view index
+ if (UtilValidate.isNotEmpty(viewIndex)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("viewIndex=" + viewIndex + "&");
+ }
+ // append view size
+ if (UtilValidate.isNotEmpty(viewSize)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("viewSize=" + viewSize + "&");
+ }
+ // append view sort
+ if (UtilValidate.isNotEmpty(viewSort)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("viewSort=" + viewSort + "&");
+ }
+ // append search string
+ if (UtilValidate.isNotEmpty(searchString)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("searchString=" + searchString + "&");
+ }
+ if (urlBuilder.toString().endsWith("&")) {
+ return urlBuilder.toString().substring(0, urlBuilder.toString().length()-1);
+ }
+
+ return urlBuilder.toString();
+ }
+
+ /**
+ * Make product url according to the configurations.
+ *
+ * @return String a catalog url
+ */
+ public static String makeProductUrl(String contextPath, List<String> trail, String productId, String productName, String currentCategoryId, String previousCategoryId) {
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(contextPath);
+ if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') {
+ urlBuilder.append("/");
+ }
+ if (!SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ urlBuilder.append(CatalogUrlServlet.CATALOG_URL_MOUNT_POINT);
+ } else {
+ urlBuilder.append(CatalogUrlServlet.PRODUCT_REQUEST + "/");
+ }
+
+ if (UtilValidate.isNotEmpty(currentCategoryId)) {
+ trail = CategoryWorker.adjustTrail(trail, currentCategoryId, previousCategoryId);
+ if (!SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ for (String trailCategoryId: trail) {
+ if ("TOP".equals(trailCategoryId)) continue;
+ urlBuilder.append("/");
+ urlBuilder.append(trailCategoryId);
+ }
+ } else {
+ if (trail.size() > 1) {
+ String lastCategoryId = trail.get(trail.size() - 1);
+ if (!"TOP".equals(lastCategoryId)) {
+ if (SeoConfigUtil.isCategoryNameEnabled()) {
+ String categoryName = CatalogUrlSeoTransform.getCategoryIdNameMap().get(lastCategoryId);
+ if (UtilValidate.isNotEmpty(categoryName)) {
+ urlBuilder.append(categoryName + URL_HYPHEN);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (UtilValidate.isNotEmpty(productId)) {
+ if (!SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ urlBuilder.append("/p_");
+ } else {
+ productName = SeoUrlUtil.replaceSpecialCharsUrl(productName);
+ if (UtilValidate.isNotEmpty(productName)) {
+ urlBuilder.append(productName + URL_HYPHEN);
+ }
+ }
+ urlBuilder.append(productId);
+ }
+
+ if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(SeoConfigUtil.getCategoryUrlSuffix())) {
+ urlBuilder.append(SeoConfigUtil.getCategoryUrlSuffix());
+ }
+
+ return urlBuilder.toString();
+ }
+
+ /**
+ * Get a string lower cased and hyphen connected.
+ *
+ * @param name a String to be transformed
+ * @return String nice name
+ */
+ protected static String getNiceName(String name) {
+ Perl5Matcher matcher = new Perl5Matcher();
+ String niceName = null;
+ if (UtilValidate.isNotEmpty(name)) {
+ name = name.trim().replaceAll(" ", URL_HYPHEN);
+ if (UtilValidate.isNotEmpty(name) && matcher.matches(name, asciiPattern)) {
+ niceName = name;
+ }
+ }
+ return niceName;
+ }
+
+ public static boolean forwardProductUri(HttpServletRequest request, HttpServletResponse response, Delegator delegator) throws ServletException, IOException {
+ return forwardProductUri(request, response, delegator, null);
+ }
+
+ public static boolean forwardProductUri(HttpServletRequest request, HttpServletResponse response, Delegator delegator, String controlServlet) throws ServletException, IOException {
+ return forwardUri(request, response, delegator, controlServlet);
+ }
+
+ /**
+ * Forward a uri according to forward pattern regular expressions.
+ *
+ * @param uri
+ * String to reverse transform
+ * @return boolean to indicate whether the uri is forwarded.
+ * @throws IOException
+ * @throws ServletException
+ */
+ public static boolean forwardUri(HttpServletRequest request, HttpServletResponse response, Delegator delegator, String controlServlet) throws ServletException, IOException {
+ String pathInfo = request.getRequestURI();
+ String contextPath = request.getContextPath();
+ if (!isCategoryMapInitialed()) {
+ initCategoryMap(request, delegator);
+ }
+
+ if (!SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ return false;
+ }
+ List<String> pathElements = StringUtil.split(pathInfo, "/");
+ if (UtilValidate.isEmpty(pathElements)) {
+ return false;
+ }
+ // remove context path
+ pathInfo = SeoUrlUtil.removeContextPath(pathInfo, contextPath);
+ // remove servlet path
+ pathInfo = SeoUrlUtil.removeContextPath(pathInfo, request.getServletPath());
+ if (pathInfo.startsWith("/" + CatalogUrlServlet.CATEGORY_REQUEST + "/")) {
+ return forwardCategoryUri(request, response, delegator, controlServlet);
+ }
+
+ String lastPathElement = pathElements.get(pathElements.size() - 1);
+ String categoryId = null;
+ String productId = null;
+ if (UtilValidate.isNotEmpty(lastPathElement)) {
+ if (UtilValidate.isNotEmpty(SeoConfigUtil.getCategoryUrlSuffix())) {
+ if (lastPathElement.endsWith(SeoConfigUtil.getCategoryUrlSuffix())) {
+ lastPathElement = lastPathElement.substring(0, lastPathElement.length() - SeoConfigUtil.getCategoryUrlSuffix().length());
+ } else {
+ return false;
+ }
+ }
+ if (SeoConfigUtil.isCategoryNameEnabled() || pathInfo.startsWith("/" + CatalogUrlServlet.CATEGORY_REQUEST + "/")) {
+ for (String categoryName : categoryNameIdMap.keySet()) {
+ if (lastPathElement.startsWith(categoryName)) {
+ categoryId = categoryNameIdMap.get(categoryName);
+ if (!lastPathElement.equals(categoryName)) {
+ lastPathElement = lastPathElement.substring(categoryName.length() + URL_HYPHEN.length());
+ }
+ break;
+ }
+ }
+ if (UtilValidate.isEmpty(categoryId)) {
+ categoryId = lastPathElement;
+ }
+ }
+
+ if (UtilValidate.isNotEmpty(lastPathElement)) {
+ List<String> urlElements = StringUtil.split(lastPathElement, URL_HYPHEN);
+ if (UtilValidate.isEmpty(urlElements)) {
+ try {
+ if (delegator.findOne("Product", UtilMisc.toMap("productId", lastPathElement), true) != null) {
+ productId = lastPathElement;
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error looking up product info for ProductUrl with path info [" + pathInfo + "]: " + e.toString(), module);
+ }
+ } else {
+ int i = urlElements.size() - 1;
+ String tempProductId = urlElements.get(i);
+ while (i >= 0) {
+ try {
+ List<EntityExpr> exprs = FastList.newInstance();
+ exprs.add(EntityCondition.makeCondition("productId", EntityOperator.EQUALS, lastPathElement));
+// if (SeoConfigUtil.isSpecialProductId(tempProductId)) {
+// exprs.add(EntityCondition.makeCondition("productId", EntityOperator.EQUALS, SeoConfigUtil.getSpecialProductId(tempProductId)));
+// } else {
+ exprs.add(EntityCondition.makeCondition("productId", EntityOperator.EQUALS, tempProductId));
+// }
+ List<GenericValue> products = delegator.findList("Product", EntityCondition.makeCondition(exprs, EntityOperator.OR), UtilMisc.toSet("productId", "productName"), null, null, true);
+
+ if (products != null && products.size() > 0) {
+ if (products.size() == 1) {
+ productId = products.get(0).getString("productId");
+ break;
+ } else {
+ productId = tempProductId;
+ break;
+ }
+ } else if (i > 0) {
+ tempProductId = urlElements.get(i - 1) + URL_HYPHEN + tempProductId;
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error looking up product info for ProductUrl with path info [" + pathInfo + "]: " + e.toString(), module);
+ }
+ i--;
+ }
+ }
+ }
+ }
+
+ if (UtilValidate.isNotEmpty(productId) || UtilValidate.isNotEmpty(categoryId)) {
+ if (categoryId != null) {
+ request.setAttribute("productCategoryId", categoryId);
+ }
+
+ if (productId != null) {
+ request.setAttribute("product_id", productId);
+ request.setAttribute("productId", productId);
+ }
+
+ StringBuilder urlBuilder = new StringBuilder();
+ if (UtilValidate.isNotEmpty(controlServlet)) {
+ urlBuilder.append("/" + controlServlet);
+ }
+ urlBuilder.append("/" + (productId != null ? CatalogUrlServlet.PRODUCT_REQUEST : CatalogUrlServlet.CATEGORY_REQUEST));
+ UrlServletHelper.setViewQueryParameters(request, urlBuilder);
+ Debug.logInfo("[Filtered request]: " + pathInfo + " (" + urlBuilder + ")", module);
+ RequestDispatcher rd = request.getRequestDispatcher(urlBuilder.toString());
+ rd.forward(request, response);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Forward a category uri according to forward pattern regular expressions.
+ *
+ * @param uri
+ * String to reverse transform
+ * @return String
+ * @throws IOException
+ * @throws ServletException
+ */
+ public static boolean forwardCategoryUri(HttpServletRequest request, HttpServletResponse response, Delegator delegator, String controlServlet) throws ServletException, IOException {
+ String pathInfo = request.getRequestURI();
+ String contextPath = request.getContextPath();
+ if (!isCategoryMapInitialed()) {
+ initCategoryMap(request);
+ }
+ if (!SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ return false;
+ }
+ List<String> pathElements = StringUtil.split(pathInfo, "/");
+ if (UtilValidate.isEmpty(pathElements)) {
+ return false;
+ }
+ String lastPathElement = pathElements.get(pathElements.size() - 1);
+ String categoryId = null;
+ if (UtilValidate.isNotEmpty(lastPathElement)) {
+ if (UtilValidate.isNotEmpty(SeoConfigUtil.getCategoryUrlSuffix())) {
+ if (lastPathElement.endsWith(SeoConfigUtil.getCategoryUrlSuffix())) {
+ lastPathElement = lastPathElement.substring(0, lastPathElement.length() - SeoConfigUtil.getCategoryUrlSuffix().length());
+ } else {
+ return false;
+ }
+ }
+ for (String categoryName : categoryNameIdMap.keySet()) {
+ if (lastPathElement.startsWith(categoryName)) {
+ categoryId = categoryNameIdMap.get(categoryName);
+ break;
+ }
+ }
+ if (UtilValidate.isEmpty(categoryId)) {
+ categoryId = lastPathElement.trim();
+ }
+ }
+ if (UtilValidate.isNotEmpty(categoryId)) {
+ request.setAttribute("productCategoryId", categoryId);
+ StringBuilder urlBuilder = new StringBuilder();
+ if (UtilValidate.isNotEmpty(controlServlet)) {
+ urlBuilder.append("/" + controlServlet);
+ }
+ urlBuilder.append("/" + CatalogUrlServlet.CATEGORY_REQUEST);
+ UrlServletHelper.setViewQueryParameters(request, urlBuilder);
+ Debug.logInfo("[Filtered request]: " + pathInfo + " (" + urlBuilder + ")", module);
+ RequestDispatcher rd = request.getRequestDispatcher(urlBuilder.toString());
+ rd.forward(request, response);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This is used when building product url in services.
+ *
+ * @param delegator
+ * @param wrapper
+ * @param prefix
+ * @param contextPath
+ * @param productCategoryId
+ * @param previousCategoryId
+ * @param productId
+ * @return
+ */
+ public static String makeProductUrl(Delegator delegator, ProductContentWrapper wrapper, String prefix, String contextPath, String currentCategoryId, String previousCategoryId,
+ String productId) {
+ StringBuilder urlBuilder = new StringBuilder();
+ GenericValue product = null;
+ urlBuilder.append(prefix);
+ if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') {
+ urlBuilder.append("/");
+ }
+ if (UtilValidate.isNotEmpty(productId)) {
+ try {
+ product = delegator.findOne("Product", UtilMisc.toMap("productId", productId), true);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Error looking up product info for productId [" + productId + "]: " + e.toString(), module);
+ }
+ }
+ if (product != null) {
+ urlBuilder.append(CatalogUrlServlet.PRODUCT_REQUEST + "/");
+ }
+
+ if (UtilValidate.isNotEmpty(currentCategoryId)) {
+ List<String> trail = null;
+ trail = CategoryWorker.adjustTrail(trail, currentCategoryId, previousCategoryId);
+ if (!SeoConfigUtil.isCategoryUrlEnabled(contextPath)) {
+ for (String trailCategoryId: trail) {
+ if ("TOP".equals(trailCategoryId)) continue;
+ urlBuilder.append("/");
+ urlBuilder.append(trailCategoryId);
+ }
+ } else {
+ if (trail != null && trail.size() > 1) {
+ String lastCategoryId = trail.get(trail.size() - 1);
+ if (!"TOP".equals(lastCategoryId)) {
+ if (SeoConfigUtil.isCategoryNameEnabled()) {
+ String categoryName = CatalogUrlSeoTransform.getCategoryIdNameMap().get(lastCategoryId);
+ if (UtilValidate.isNotEmpty(categoryName)) {
+ urlBuilder.append(categoryName);
+ if (product != null) {
+ urlBuilder.append(URL_HYPHEN);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (UtilValidate.isNotEmpty(productId)) {
+ if (product != null) {
+ String productName = product.getString("productName");
+ productName = SeoUrlUtil.replaceSpecialCharsUrl(productName);
+ if (UtilValidate.isNotEmpty(productName)) {
+ urlBuilder.append(productName + URL_HYPHEN);
+ } else {
+ StringWrapper alternativeUrl = wrapper.get("ALTERNATIVE_URL");
+ if (UtilValidate.isNotEmpty(alternativeUrl) && UtilValidate.isNotEmpty(alternativeUrl.toString())) {
+ productName = SeoUrlUtil.replaceSpecialCharsUrl(alternativeUrl.toString());
+ if (UtilValidate.isNotEmpty(productName)) {
+ urlBuilder.append(productName + URL_HYPHEN);
+ }
+ }
+ }
+ }
+ try {
+ //SeoConfigUtil.addSpecialProductId(productId);
+ urlBuilder.append(productId);
+ } catch (Exception e) {
+ urlBuilder.append(productId);
+ }
+ }
+
+ if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(SeoConfigUtil.getCategoryUrlSuffix())) {
+ urlBuilder.append(SeoConfigUtil.getCategoryUrlSuffix());
+ }
+
+ return urlBuilder.toString();
+ }
+
+ /**
+ * This is used when building category url in services.
+ *
+ * @param delegator
+ * @param wrapper
+ * @param prefix
+ * @param productCategoryId
+ * @param previousCategoryId
+ * @param productId
+ * @param viewSize
+ * @param viewIndex
+ * @param viewSort
+ * @param searchString
+ * @return
+ */
+ public static String makeCategoryUrl(Delegator delegator, CategoryContentWrapper wrapper, String prefix,
+ String currentCategoryId, String previousCategoryId, String productId, String viewSize, String viewIndex,
+ String viewSort, String searchString) {
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(prefix);
+ if (urlBuilder.charAt(urlBuilder.length() - 1) != '/') {
+ urlBuilder.append("/");
+ }
+ urlBuilder.append(CatalogUrlServlet.CATEGORY_REQUEST + "/");
+
+ if (UtilValidate.isNotEmpty(currentCategoryId)) {
+ List<String> trail = null;
+ trail = CategoryWorker.adjustTrail(trail, currentCategoryId, previousCategoryId);
+ if (trail != null && trail.size() > 1) {
+ String lastCategoryId = trail.get(trail.size() - 1);
+ if (!"TOP".equals(lastCategoryId)) {
+ String categoryName = CatalogUrlSeoTransform.getCategoryIdNameMap().get(lastCategoryId);
+ if (UtilValidate.isNotEmpty(categoryName)) {
+ urlBuilder.append(categoryName);
+ urlBuilder.append(URL_HYPHEN);
+ urlBuilder.append(lastCategoryId.trim().replaceAll(" ", URL_HYPHEN));
+ } else {
+ urlBuilder.append(lastCategoryId.trim().replaceAll(" ", URL_HYPHEN));
+ }
+ }
+ }
+ }
+
+ if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(SeoConfigUtil.getCategoryUrlSuffix())) {
+ urlBuilder.append(SeoConfigUtil.getCategoryUrlSuffix());
+ }
+
+ // append view index
+ if (UtilValidate.isNotEmpty(viewIndex)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("viewIndex=" + viewIndex + "&");
+ }
+ // append view size
+ if (UtilValidate.isNotEmpty(viewSize)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("viewSize=" + viewSize + "&");
+ }
+ // append view sort
+ if (UtilValidate.isNotEmpty(viewSort)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("viewSort=" + viewSort + "&");
+ }
+ // append search string
+ if (UtilValidate.isNotEmpty(searchString)) {
+ if (!urlBuilder.toString().endsWith("?") && !urlBuilder.toString().endsWith("&")) {
+ urlBuilder.append("?");
+ }
+ urlBuilder.append("searchString=" + searchString + "&");
+ }
+ if (urlBuilder.toString().endsWith("&")) {
+ return urlBuilder.toString().substring(0, urlBuilder.toString().length()-1);
+ }
+
+ return urlBuilder.toString();
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/OfbizCatalogAltUrlTransform.java b/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java
similarity index 89%
rename from applications/product/src/org/ofbiz/product/category/OfbizCatalogAltUrlTransform.java
rename to applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java
index f8f6373..3081381 100644
--- a/applications/product/src/org/ofbiz/product/category/OfbizCatalogAltUrlTransform.java
+++ b/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
-package org.ofbiz.product.category;
+package org.ofbiz.product.category.ftl;
import java.io.IOException;
import java.io.Writer;
@@ -25,13 +25,15 @@
import javax.servlet.http.HttpServletRequest;
+import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.template.FreeMarkerWorker;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
-import org.ofbiz.entity.util.EntityQuery;
+import org.ofbiz.product.category.CatalogUrlFilter;
+import org.ofbiz.product.category.CategoryContentWrapper;
import org.ofbiz.product.product.ProductContentWrapper;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.webapp.OfbizUrlBuilder;
@@ -49,7 +51,6 @@
public class OfbizCatalogAltUrlTransform implements TemplateTransformModel {
public final static String module = OfbizCatalogUrlTransform.class.getName();
- @SuppressWarnings("unchecked")
public String getStringArg(Map args, String key) {
Object o = args.get(key);
if (o instanceof SimpleScalar) {
@@ -78,7 +79,6 @@
}
@Override
- @SuppressWarnings("unchecked")
public Writer getWriter(final Writer out, final Map args)
throws TemplateModelException, IOException {
final StringBuilder buf = new StringBuilder();
@@ -122,8 +122,12 @@
}
// make the link
if (fullPath){
- OfbizUrlBuilder builder = OfbizUrlBuilder.from(request);
- builder.buildHostPart(newURL, url, secure);
+ try {
+ OfbizUrlBuilder builder = OfbizUrlBuilder.from(request);
+ builder.buildHostPart(newURL, "", secure);
+ } catch (WebAppConfigurationException e) {
+ Debug.logError(e.getMessage(), module);
+ }
}
newURL.append(url);
out.write(newURL.toString());
@@ -132,11 +136,11 @@
LocalDispatcher dispatcher = FreeMarkerWorker.getWrappedObject("dispatcher", env);
Locale locale = (Locale) args.get("locale");
if (UtilValidate.isNotEmpty(productId)) {
- GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne();
+ GenericValue product = delegator.findOne("Product", UtilMisc.toMap("productId", productId), false);
ProductContentWrapper wrapper = new ProductContentWrapper(dispatcher, product, locale, "text/html");
url = CatalogUrlFilter.makeProductUrl(delegator, wrapper, null, ((StringModel) prefix).getAsString(), previousCategoryId, productCategoryId, productId);
} else {
- GenericValue productCategory = EntityQuery.use(delegator).from("ProductCategory").where("productCategoryId", productCategoryId).queryOne();
+ GenericValue productCategory = delegator.findOne("ProductCategory", UtilMisc.toMap("productCategoryId", productCategoryId), false);
CategoryContentWrapper wrapper = new CategoryContentWrapper(dispatcher, productCategory, locale, "text/html");
url = CatalogUrlFilter.makeCategoryUrl(delegator, wrapper, null, ((StringModel) prefix).getAsString(), previousCategoryId, productCategoryId, productId, viewSize, viewIndex, viewSort, searchString);
}
@@ -148,8 +152,6 @@
throw new IOException(e.getMessage());
} catch (GenericEntityException e) {
throw new IOException(e.getMessage());
- } catch (WebAppConfigurationException e) {
- throw new IOException(e.getMessage());
}
}
};
diff --git a/applications/product/src/org/ofbiz/product/category/OfbizCatalogUrlTransform.java b/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java
similarity index 96%
rename from applications/product/src/org/ofbiz/product/category/OfbizCatalogUrlTransform.java
rename to applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java
index bd9c451..a51708e 100644
--- a/applications/product/src/org/ofbiz/product/category/OfbizCatalogUrlTransform.java
+++ b/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
-package org.ofbiz.product.category;
+package org.ofbiz.product.category.ftl;
import java.io.IOException;
import java.io.Writer;
@@ -24,6 +24,8 @@
import javax.servlet.http.HttpServletRequest;
+import org.ofbiz.product.category.CatalogUrlServlet;
+
import freemarker.core.Environment;
import freemarker.ext.beans.BeanModel;
import freemarker.ext.beans.StringModel;
@@ -34,7 +36,6 @@
public class OfbizCatalogUrlTransform implements TemplateTransformModel {
public final static String module = OfbizCatalogUrlTransform.class.getName();
- @SuppressWarnings("unchecked")
public String getStringArg(Map args, String key) {
Object o = args.get(key);
if (o instanceof SimpleScalar) {
@@ -46,7 +47,6 @@
}
@Override
- @SuppressWarnings("unchecked")
public Writer getWriter(final Writer out, final Map args) throws TemplateModelException, IOException {
final StringBuilder buf = new StringBuilder();
return new Writer(out) {
diff --git a/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java b/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java
new file mode 100644
index 0000000..1f78087
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * 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.ftl;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.Perl5Matcher;
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.entity.GenericValue;
+import org.ofbiz.product.category.SeoConfigUtil;
+import org.ofbiz.webapp.control.RequestHandler;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.BeanModel;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateScalarModel;
+import freemarker.template.TemplateTransformModel;
+
+/**
+ * SeoTransform - Freemarker Transform for URLs (links)
+ *
+ */
+public class SeoTransform implements TemplateTransformModel {
+
+ private static final String module = SeoTransform.class.getName();
+
+ public boolean checkArg(Map args, String key, boolean defaultValue) {
+ if (!args.containsKey(key)) {
+ return defaultValue;
+ } else {
+ Object o = args.get(key);
+ if (o instanceof SimpleScalar) {
+ SimpleScalar s = (SimpleScalar) o;
+ return "true".equalsIgnoreCase(s.getAsString());
+ }
+ return defaultValue;
+ }
+ }
+
+ public Writer getWriter(final Writer out, Map args) {
+ final StringBuffer buf = new StringBuffer();
+ final boolean fullPath = checkArg(args, "fullPath", false);
+ final boolean secure = checkArg(args, "secure", false);
+ final boolean encode = checkArg(args, "encode", true);
+
+ return new Writer(out) {
+
+ public void write(char cbuf[], int off, int len) {
+ buf.append(cbuf, off, len);
+ }
+
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ public void close() throws IOException {
+ try {
+ Environment env = Environment.getCurrentEnvironment();
+ BeanModel req = (BeanModel) env.getVariable("request");
+ BeanModel res = (BeanModel) env.getVariable("response");
+ Object prefix = env.getVariable("urlPrefix");
+ if (req != null) {
+ HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
+ ServletContext ctx = (ServletContext) request.getAttribute("servletContext");
+ HttpServletResponse response = null;
+ if (res != null) {
+ response = (HttpServletResponse) res.getWrappedObject();
+ }
+ HttpSession session = request.getSession();
+ GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
+
+ // anonymous shoppers are not logged in
+ if (userLogin != null && "anonymous".equals(userLogin.getString("userLoginId"))) {
+ userLogin = null;
+ }
+
+ RequestHandler rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_");
+ out.write(seoUrl(rh.makeLink(request, response, buf.toString(), fullPath, secure, encode), userLogin == null));
+ } else if (prefix != null) {
+ if (prefix instanceof TemplateScalarModel) {
+ TemplateScalarModel s = (TemplateScalarModel) prefix;
+ String prefixString = s.getAsString();
+ String bufString = buf.toString();
+ boolean prefixSlash = prefixString.endsWith("/");
+ boolean bufSlash = bufString.startsWith("/");
+ if (prefixSlash && bufSlash) {
+ bufString = bufString.substring(1);
+ } else if (!prefixSlash && !bufSlash) {
+ bufString = "/" + bufString;
+ }
+ out.write(prefixString + bufString);
+ }
+ } else {
+ out.write(buf.toString());
+ }
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+ };
+ }
+
+ /**
+ * Transform a url according to seo pattern regular expressions.
+ *
+ * @param url , String to do the seo transform
+ * @param isAnon , boolean to indicate whether it's an anonymous visit.
+ *
+ * @return String, the transformed url.
+ */
+ public static String seoUrl(String url, boolean isAnon) {
+ Perl5Matcher matcher = new Perl5Matcher();
+ if (SeoConfigUtil.checkUseUrlRegexp() && matcher.matches(url, SeoConfigUtil.getGeneralRegexpPattern())) {
+ Iterator<String> keys = SeoConfigUtil.getSeoPatterns().keySet().iterator();
+ boolean foundMatch = false;
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Pattern pattern = SeoConfigUtil.getSeoPatterns().get(key);
+ if (pattern.getPattern().contains(";jsessionid=")) {
+ if (isAnon) {
+ if (SeoConfigUtil.isJSessionIdAnonEnabled()) {
+ continue;
+ }
+ } else {
+ if (SeoConfigUtil.isJSessionIdUserEnabled()) {
+ continue;
+ } else {
+ boolean foundException = false;
+ for (int i = 0; i < SeoConfigUtil.getUserExceptionPatterns().size(); i++) {
+ if (matcher.matches(url, SeoConfigUtil.getUserExceptionPatterns().get(i))) {
+ foundException = true;
+ break;
+ }
+ }
+ if (foundException) {
+ continue;
+ }
+ }
+ }
+ }
+ String replacement = SeoConfigUtil.getSeoReplacements().get(key);
+ if (matcher.matches(url, pattern)) {
+ for (int i = 1; i < matcher.getMatch().groups(); i++) {
+ replacement = replacement.replaceAll("\\$" + i, matcher.getMatch().group(i));
+ }
+ // break if found any matcher
+ url = replacement;
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ Debug.logVerbose("Can NOT find a seo transform pattern for this url: " + url, module);
+ }
+ }
+ return url;
+ }
+
+ static {
+ if (!SeoConfigUtil.isInitialed()) {
+ SeoConfigUtil.init();
+ }
+ }
+}
diff --git a/applications/product/src/org/ofbiz/product/category/ftl/UrlRegexpTransform.java b/applications/product/src/org/ofbiz/product/category/ftl/UrlRegexpTransform.java
new file mode 100644
index 0000000..58f5547
--- /dev/null
+++ b/applications/product/src/org/ofbiz/product/category/ftl/UrlRegexpTransform.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * 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.ftl;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.Perl5Matcher;
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.entity.GenericValue;
+import org.ofbiz.product.category.SeoConfigUtil;
+import org.ofbiz.webapp.control.RequestHandler;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.BeanModel;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateScalarModel;
+import freemarker.template.TemplateTransformModel;
+
+/**
+ * UrlRegexpTransform - Freemarker Transform for Products URLs (links)
+ *
+ */
+public class UrlRegexpTransform implements TemplateTransformModel {
+
+ private static final String module = UrlRegexpTransform.class.getName();
+
+ public boolean checkArg(Map args, String key, boolean defaultValue) {
+ if (!args.containsKey(key)) {
+ return defaultValue;
+ } else {
+ Object o = args.get(key);
+ if (o instanceof SimpleScalar) {
+ SimpleScalar s = (SimpleScalar) o;
+ return "true".equalsIgnoreCase(s.getAsString());
+ }
+ return defaultValue;
+ }
+ }
+
+ public Writer getWriter(final Writer out, Map args) {
+ final StringBuffer buf = new StringBuffer();
+ final boolean fullPath = checkArg(args, "fullPath", false);
+ final boolean secure = checkArg(args, "secure", false);
+ final boolean encode = checkArg(args, "encode", true);
+
+ return new Writer(out) {
+
+ public void write(char cbuf[], int off, int len) {
+ buf.append(cbuf, off, len);
+ }
+
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ public void close() throws IOException {
+ try {
+ Environment env = Environment.getCurrentEnvironment();
+ BeanModel req = (BeanModel) env.getVariable("request");
+ BeanModel res = (BeanModel) env.getVariable("response");
+ Object prefix = env.getVariable("urlPrefix");
+ if (req != null) {
+ HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
+ ServletContext ctx = (ServletContext) request.getAttribute("servletContext");
+ HttpServletResponse response = null;
+ if (res != null) {
+ response = (HttpServletResponse) res.getWrappedObject();
+ }
+ HttpSession session = request.getSession();
+ GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
+
+ // anonymous shoppers are not logged in
+ if (userLogin != null && "anonymous".equals(userLogin.getString("userLoginId"))) {
+ userLogin = null;
+ }
+
+ RequestHandler rh = (RequestHandler) ctx.getAttribute("_REQUEST_HANDLER_");
+ out.write(seoUrl(rh.makeLink(request, response, buf.toString(), fullPath, secure, encode), userLogin == null));
+ } else if (prefix != null) {
+ if (prefix instanceof TemplateScalarModel) {
+ TemplateScalarModel s = (TemplateScalarModel) prefix;
+ String prefixString = s.getAsString();
+ String bufString = buf.toString();
+ boolean prefixSlash = prefixString.endsWith("/");
+ boolean bufSlash = bufString.startsWith("/");
+ if (prefixSlash && bufSlash) {
+ bufString = bufString.substring(1);
+ } else if (!prefixSlash && !bufSlash) {
+ bufString = "/" + bufString;
+ }
+ out.write(prefixString + bufString);
+ }
+ } else {
+ out.write(buf.toString());
+ }
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+ };
+ }
+
+ /**
+ * Transform a url according to seo pattern regular expressions.
+ *
+ * @param url
+ * , String to do the seo transform
+ * @param isAnon
+ * , boolean to indicate whether it's an anonymous visit.
+ *
+ * @return String, the transformed url.
+ */
+ public static String seoUrl(String url, boolean isAnon) {
+ Perl5Matcher matcher = new Perl5Matcher();
+ if (SeoConfigUtil.checkUseUrlRegexp() && matcher.matches(url, SeoConfigUtil.getGeneralRegexpPattern())) {
+ Iterator<String> keys = SeoConfigUtil.getSeoPatterns().keySet().iterator();
+ boolean foundMatch = false;
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Pattern pattern = SeoConfigUtil.getSeoPatterns().get(key);
+ if (pattern.getPattern().contains(";jsessionid=")) {
+ if (isAnon) {
+ if (SeoConfigUtil.isJSessionIdAnonEnabled()) {
+ continue;
+ }
+ } else {
+ if (SeoConfigUtil.isJSessionIdUserEnabled()) {
+ continue;
+ } else {
+ boolean foundException = false;
+ for (int i = 0; i < SeoConfigUtil.getUserExceptionPatterns().size(); i++) {
+ if (matcher.matches(url, SeoConfigUtil.getUserExceptionPatterns().get(i))) {
+ foundException = true;
+ break;
+ }
+ }
+ if (foundException) {
+ continue;
+ }
+ }
+ }
+ }
+ String replacement = SeoConfigUtil.getSeoReplacements().get(key);
+ if (matcher.matches(url, pattern)) {
+ for (int i = 1; i < matcher.getMatch().groups(); i++) {
+ replacement = replacement.replaceAll("\\$" + i, matcher.getMatch().group(i));
+ }
+ // break if found any matcher
+ url = replacement;
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ Debug.logVerbose("Can NOT find a seo transform pattern for this url: " + url, module);
+ }
+ }
+ return url;
+ }
+
+ static {
+ SeoConfigUtil.init();
+ }
+
+ /**
+ * Forward a uri according to forward pattern regular expressions. Note: this is developed for Filter usage.
+ *
+ * @param uri
+ * String to reverse transform
+ * @return String
+ */
+ public static boolean forwardUri(HttpServletResponse response, String uri) {
+ Perl5Matcher matcher = new Perl5Matcher();
+ boolean foundMatch = false;
+ Integer responseCodeInt = null;
+ if (SeoConfigUtil.checkUseUrlRegexp() && SeoConfigUtil.getSeoPatterns() != null && SeoConfigUtil.getForwardReplacements() != null) {
+ Iterator<String> keys = SeoConfigUtil.getSeoPatterns().keySet().iterator();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Pattern pattern = SeoConfigUtil.getSeoPatterns().get(key);
+ String replacement = SeoConfigUtil.getForwardReplacements().get(key);
+ if (matcher.matches(uri, pattern)) {
+ for (int i = 1; i < matcher.getMatch().groups(); i++) {
+ replacement = replacement.replaceAll("\\$" + i, matcher.getMatch().group(i));
+ }
+ // break if found any matcher
+ uri = replacement;
+ responseCodeInt = SeoConfigUtil.getForwardResponseCodes().get(key);
+ foundMatch = true;
+ break;
+ }
+ }
+ }
+ if (foundMatch) {
+ if (responseCodeInt == null) {
+ response.setStatus(SeoConfigUtil.DEFAULT_RESPONSECODE);
+ } else {
+ response.setStatus(responseCodeInt.intValue());
+ }
+ response.setHeader("Location", uri);
+ } else {
+ Debug.logInfo("Can NOT forward this url: " + uri, module);
+ }
+ return foundMatch;
+ }
+}
diff --git a/applications/product/widget/catalog/CatalogMenus.xml b/applications/product/widget/catalog/CatalogMenus.xml
index 545de0d..3e144f4 100644
--- a/applications/product/widget/catalog/CatalogMenus.xml
+++ b/applications/product/widget/catalog/CatalogMenus.xml
@@ -532,9 +532,7 @@
<condition>
<not><if-empty field="product"/></not>
</condition>
- <link target="/ecommerce/control/product" url-mode="inter-app">
- <parameter param-name="product_id" from-field="productId"/>
- </link>
+ <link target="/ecommerce/product/${ecomLink}" url-mode="inter-app" />
</menu-item>
<menu-item name="ProductBarCode" title="${uiLabelMap.ProductBarcode}">
<condition>
diff --git a/applications/product/widget/catalog/ProductScreens.xml b/applications/product/widget/catalog/ProductScreens.xml
index 7e6b63f..b6f6e89 100644
--- a/applications/product/widget/catalog/ProductScreens.xml
+++ b/applications/product/widget/catalog/ProductScreens.xml
@@ -33,6 +33,8 @@
<set field="productId" from-field="parameters.productId" global="true"/>
<entity-one entity-name="Product" value-field="product"/>
<set field="product" from-field="product" global="true"/>
+ <set field="ecomLink" value="${groovy: product.internalName.replace(' ', org.ofbiz.product.category.ftl.CatalogUrlSeoTransform.URL_HYPHEN)
+ + org.ofbiz.product.category.ftl.CatalogUrlSeoTransform.URL_HYPHEN + productId + '.html'}"/>
</actions>
<widgets>
<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
diff --git a/framework/webapp/config/freemarkerTransforms.properties b/framework/webapp/config/freemarkerTransforms.properties
index ae3a32a..3866359 100644
--- a/framework/webapp/config/freemarkerTransforms.properties
+++ b/framework/webapp/config/freemarkerTransforms.properties
@@ -21,7 +21,8 @@
# entries are in the form: key=transform name, property=transform class name
-ofbizUrl=org.ofbiz.webapp.ftl.OfbizUrlTransform
+#ofbizUrl=org.ofbiz.webapp.ftl.OfbizUrlTransform
+ofbizUrl=org.ofbiz.product.category.ftl.UrlRegexpTransform
ofbizContentUrl=org.ofbiz.webapp.ftl.OfbizContentTransform
ofbizCurrency=org.ofbiz.webapp.ftl.OfbizCurrencyTransform
ofbizAmount=org.ofbiz.webapp.ftl.OfbizAmountTransform
diff --git a/framework/webapp/src/org/ofbiz/webapp/WebAppUtil.java b/framework/webapp/src/org/ofbiz/webapp/WebAppUtil.java
index 0e0adce..cc62d08 100644
--- a/framework/webapp/src/org/ofbiz/webapp/WebAppUtil.java
+++ b/framework/webapp/src/org/ofbiz/webapp/WebAppUtil.java
@@ -66,7 +66,7 @@
String servletMapping = null;
WebXml webXml = getWebXml(webAppInfo);
for (ServletDef servletDef : webXml.getServlets().values()) {
- if ("org.ofbiz.webapp.control.ControlServlet".equals(servletDef.getServletClass())) {
+ if ("org.ofbiz.webapp.control.ControlServlet".equals(servletDef.getServletClass()) || "org.ofbiz.product.category.SeoControlServlet".equals(servletDef.getServletClass())) {
String servletName = servletDef.getServletName();
// Catalina servlet mappings: key = url-pattern, value = servlet-name.
for (Entry<String, String> entry : webXml.getServletMappings().entrySet()) {
diff --git a/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java b/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java
index ad02bdc..6e8546c 100644
--- a/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java
+++ b/framework/webapp/src/org/ofbiz/webapp/control/RequestHandler.java
@@ -862,7 +862,7 @@
}
private void renderView(String view, boolean allowExtView, HttpServletRequest req, HttpServletResponse resp, String saveName) throws RequestHandlerException {
GenericValue userLogin = (GenericValue) req.getSession().getAttribute("userLogin");
- // workaraound if we are in the root webapp
+ // workaround if we are in the root webapp
String cname = UtilHttp.getApplicationName(req);
String oldView = view;
@@ -873,12 +873,11 @@
// if the view name starts with the control servlet name and a /, then it was an
// attempt to override the default view with a call back into the control servlet,
// so just get the target view name and use that
-
String servletName = req.getServletPath();
- if (servletName.startsWith("/")) {
+ if (UtilValidate.isNotEmpty(servletName) && servletName.length() > 1 || servletName.startsWith("/")) {
servletName = servletName.substring(1);
}
-
+
if (Debug.infoOn()) Debug.logInfo("Rendering View [" + view + "], sessionId=" + UtilHttp.getSessionId(req), module);
if (view.startsWith(servletName + "/")) {
view = view.substring(servletName.length() + 1);
diff --git a/specialpurpose/ecommerce/src/org/ofbiz/ecommerce/webapp/view/JspViewHandler.java b/specialpurpose/ecommerce/src/org/ofbiz/ecommerce/webapp/view/JspViewHandler.java
new file mode 100644
index 0000000..e4cb1f9
--- /dev/null
+++ b/specialpurpose/ecommerce/src/org/ofbiz/ecommerce/webapp/view/JspViewHandler.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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.ecommerce.webapp.view;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.JspException;
+
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.UtilValidate;
+import org.ofbiz.webapp.control.ContextFilter;
+import org.ofbiz.webapp.view.AbstractViewHandler;
+import org.ofbiz.webapp.view.ViewHandlerException;
+
+/**
+ * JspViewHandler - Java Server Pages View Handler
+ */
+public class JspViewHandler extends AbstractViewHandler {
+
+ public static final String module = JspViewHandler.class.getName();
+
+ protected ServletContext context;
+
+ public void init(ServletContext context) throws ViewHandlerException {
+ this.context = context;
+ }
+
+ public void render(String name, String page, String contentType, String encoding, String info, HttpServletRequest request, HttpServletResponse response) throws ViewHandlerException {
+ // some containers call filters on EVERY request, even forwarded ones,
+ // so let it know that it came from the control servlet
+
+ if (request == null) {
+ throw new ViewHandlerException("Null HttpServletRequest object");
+ }
+ if (UtilValidate.isEmpty(page)) {
+ throw new ViewHandlerException("Null or empty source");
+ }
+
+ // Debug.logInfo("Requested Page : " + page, module);
+ // Debug.logInfo("Physical Path : " + context.getRealPath(page));
+
+ // tell the ContextFilter we are forwarding
+ request.setAttribute(ContextFilter.FORWARDED_FROM_SERVLET, Boolean.TRUE);
+ RequestDispatcher rd = request.getRequestDispatcher(page);
+
+ if (rd == null) {
+ Debug.logInfo("HttpServletRequest.getRequestDispatcher() failed; trying ServletContext", module);
+ rd = context.getRequestDispatcher(page);
+ if (rd == null) {
+ Debug.logInfo("ServletContext.getRequestDispatcher() failed; trying ServletContext.getNamedDispatcher(\"jsp\")", module);
+ rd = context.getNamedDispatcher("jsp");
+ if (rd == null) {
+ throw new ViewHandlerException("Source returned a null dispatcher (" + page + ")");
+ }
+ }
+ }
+
+ try {
+ if (UtilValidate.isEmpty(request.getServletPath())) {
+ // no context or filter to service this page, so we have to forward it directly and let SeoControlServlet to resolve it
+ String uri = URLEncoder.encode(request.getContextPath() + page, "UTF-8");
+ request.setAttribute("_jsp_" + uri, Boolean.TRUE);
+ rd.forward(request, response);
+ } else {
+ rd.include(request, response);
+ }
+ } catch (IOException ie) {
+ throw new ViewHandlerException("IO Error in view", ie);
+ } catch (ServletException e) {
+ Throwable throwable = e.getRootCause() != null ? e.getRootCause() : e;
+
+ if (throwable instanceof JspException) {
+ JspException jspe = (JspException) throwable;
+
+ throwable = jspe.getCause() != null ? jspe.getCause() : jspe;
+ }
+ Debug.logError(throwable, "ServletException rendering JSP view", module);
+ throw new ViewHandlerException(e.getMessage(), throwable);
+ }
+ }
+}
diff --git a/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml b/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml
index 7819d50..a938312 100644
--- a/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml
+++ b/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml
@@ -31,7 +31,7 @@
<handler name="simple" type="request" class="org.ofbiz.webapp.event.SimpleEventHandler"/>
<handler name="rome" type="request" class="org.ofbiz.webapp.event.RomeEventHandler"/>
- <handler name="jsp" type="view" class="org.ofbiz.webapp.view.JspViewHandler"/>
+ <handler name="jsp" type="view" class="org.ofbiz.ecommerce.webapp.view.JspViewHandler"/>
<handler name="http" type="view" class="org.ofbiz.webapp.view.HttpViewHandler"/>
<handler name="screen" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>
<handler name="simplecontent" type="view" class="org.ofbiz.content.view.SimpleContentViewHandler"/>
@@ -2020,7 +2020,7 @@
<!-- End of Request Mappings -->
<!-- View Mappings -->
- <view-map name="error" page="/error/error.jsp"/>
+ <view-map name="error" type="jsp" page="/error/error.jsp"/>
<view-map name="main" type="screen" page="component://ecommerce/widget/CommonScreens.xml#main"/>
<view-map name="policies" type="screen" page="component://ecommerce/widget/CommonScreens.xml#policies"/>
<view-map name="login" type="screen" page="component://ecommerce/widget/CommonScreens.xml#login"/>
diff --git a/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/web.xml b/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/web.xml
index da3b16d..ba92866 100644
--- a/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/web.xml
+++ b/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/web.xml
@@ -52,17 +52,23 @@
frequently switch between http and https.
</description>
</context-param>
+ <context-param>
+ <param-name>defaultPage</param-name>
+ <param-value>/main</param-value>
+ <description>Default page uri. Important: please DO add or remove /control to match url-pattern of SeoControlServlet.
+ </description>
+ </context-param>
<filter>
- <filter-name>CatalogUrlFilter</filter-name>
- <display-name>CatalogUrlFilter</display-name>
- <filter-class>org.ofbiz.product.category.CatalogUrlFilter</filter-class>
+ <filter-name>SeoCatalogUrlFilter</filter-name>
+ <display-name>SeoCatalogUrlFilter</display-name>
+ <filter-class>org.ofbiz.product.category.CatalogUrlSeoFilter</filter-class>
<init-param><param-name>defaultLocaleString</param-name><param-value>en_US</param-value></init-param>
</filter>
<filter>
- <filter-name>ContentUrlFilter</filter-name>
- <display-name>ContentUrlFilter</display-name>
- <filter-class>org.ofbiz.content.content.ContentUrlFilter</filter-class>
+ <filter-name>SeoContentUrlFilter</filter-name>
+ <display-name>SeoContentUrlFilter</display-name>
+ <filter-class>org.ofbiz.product.category.SeoContentUrlFilter</filter-class>
<init-param>
<param-name>defaultLocaleString</param-name>
<param-value>en_US</param-value>
@@ -70,9 +76,9 @@
<init-param><param-name>viewRequest</param-name><param-value>ViewBlogArticle</param-value></init-param>
</filter>
<filter>
- <filter-name>ContextFilter</filter-name>
- <display-name>ContextFilter</display-name>
- <filter-class>org.ofbiz.webapp.control.ContextFilter</filter-class>
+ <filter-name>SeoContextFilter</filter-name>
+ <display-name>SeoContextFilter</display-name>
+ <filter-class>org.ofbiz.product.category.SeoContextFilter</filter-class>
<init-param>
<param-name>disableContextSecurity</param-name>
<param-value>N</param-value>
@@ -83,15 +89,15 @@
</init-param>
</filter>
<filter-mapping>
- <filter-name>CatalogUrlFilter</filter-name>
+ <filter-name>SeoCatalogUrlFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
- <filter-name>ContentUrlFilter</filter-name>
+ <filter-name>SeoContentUrlFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
- <filter-name>ContextFilter</filter-name>
+ <filter-name>SeoContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
@@ -105,10 +111,10 @@
<listener><listener-class>org.ofbiz.webapp.control.LoginEventListener</listener-class></listener>
<servlet>
- <servlet-name>ControlServlet</servlet-name>
- <display-name>ControlServlet</display-name>
- <description>Main Control Servlet</description>
- <servlet-class>org.ofbiz.webapp.control.ControlServlet</servlet-class>
+ <servlet-name>SeoControlServlet</servlet-name>
+ <display-name>SeoControlServlet</display-name>
+ <description>Main SEO Control Servlet</description>
+ <servlet-class>org.ofbiz.product.category.SeoControlServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- un-comment for Worldpay
@@ -121,16 +127,16 @@
</servlet>
-->
<servlet>
- <servlet-name>CatalogUrlServlet</servlet-name>
- <display-name>CatalogUrlServlet</display-name>
- <description>Catalog (Category/Product) URL Servlet</description>
- <servlet-class>org.ofbiz.product.category.CatalogUrlServlet</servlet-class>
+ <servlet-name>SeoCatalogUrlServlet</servlet-name>
+ <display-name>SeoCatalogUrlServlet</display-name>
+ <description>SEO Catalog (Category/Product) URL Servlet</description>
+ <servlet-class>org.ofbiz.product.category.SeoCatalogUrlServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
- <servlet-name>ControlServlet</servlet-name>
- <url-pattern>/control/*</url-pattern>
+ <servlet-name>SeoControlServlet</servlet-name>
+ <url-pattern>/*</url-pattern>
</servlet-mapping>
<!-- un-comment for Worldpay
<servlet-mapping>
@@ -139,7 +145,7 @@
</servlet-mapping>
-->
<servlet-mapping>
- <servlet-name>CatalogUrlServlet</servlet-name>
+ <servlet-name>SeoCatalogUrlServlet</servlet-name>
<url-pattern>/products/*</url-pattern>
</servlet-mapping>
diff --git a/specialpurpose/ecommerce/webapp/ecommerce/index.jsp b/specialpurpose/ecommerce/webapp/ecommerce/index.jsp
index b8ade91..7cb5e40 100644
--- a/specialpurpose/ecommerce/webapp/ecommerce/index.jsp
+++ b/specialpurpose/ecommerce/webapp/ecommerce/index.jsp
@@ -17,4 +17,4 @@
under the License.
--%>
-<%pageContext.forward("control/main");%>
+<%response.sendRedirect("main");%>