svn commit: r1535171 [3/4] - in /ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23: ./ applications/product/ applications/product/config/ applications/product/dtd/ applications/product/src/org/ofbiz/product/category/ applications/product/src/org...

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1535171 [3/4] - in /ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23: ./ applications/product/ applications/product/config/ applications/product/dtd/ applications/product/src/org/ofbiz/product/category/ applications/product/src/org...

jleroux@apache.org
Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java?rev=1535171&view=auto
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java (added)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java Wed Oct 23 20:48:36 2013
@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * 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
+        String disableCachedClassloader = config.getInitParameter("disableCachedClassloader");
+        if (disableCachedClassloader == null || !"Y".equalsIgnoreCase(disableCachedClassloader)) {
+            Thread.currentThread().setContextClassLoader(localCachedClassLoader);
+        }
+
+        // set the ServletContext in the request for future use
+        httpRequest.setAttribute("servletContext", config.getServletContext());
+
+        // set the webSiteId in the session
+        if (UtilValidate.isEmpty(httpRequest.getSession().getAttribute("webSiteId"))) {
+            httpRequest.getSession().setAttribute("webSiteId", config.getServletContext().getAttribute("webSiteId"));
+        }
+
+        // set the filesystem path of context root.
+        httpRequest.setAttribute("_CONTEXT_ROOT_", config.getServletContext().getRealPath("/"));
+
+        // set the server root url
+        StringBuffer serverRootUrl = UtilHttp.getServerRootUrl(httpRequest);
+        httpRequest.setAttribute("_SERVER_ROOT_URL_", serverRootUrl.toString());
+
+        // request attributes from redirect call
+        String reqAttrMapHex = (String) httpRequest.getSession().getAttribute("_REQ_ATTR_MAP_");
+        if (UtilValidate.isNotEmpty(reqAttrMapHex)) {
+            byte[] reqAttrMapBytes = StringUtil.fromHexString(reqAttrMapHex);
+            Map<String, Object> reqAttrMap = checkMap(UtilObject.getObject(reqAttrMapBytes), String.class, Object.class);
+            if (reqAttrMap != null) {
+                for (Map.Entry<String, Object> entry : reqAttrMap.entrySet()) {
+                    httpRequest.setAttribute(entry.getKey(), entry.getValue());
+                }
+            }
+            httpRequest.getSession().removeAttribute("_REQ_ATTR_MAP_");
+        }
+
+        // ----- Context Security -----
+        // check if we are disabled
+        String disableSecurity = config.getInitParameter("disableContextSecurity");
+        if (disableSecurity != null && "Y".equalsIgnoreCase(disableSecurity)) {
+            chain.doFilter(httpRequest, httpResponse);
+            return;
+        }
+
+        // check if we are told to redirect everthing
+        String redirectAllTo = config.getInitParameter("forceRedirectAll");
+        if (UtilValidate.isNotEmpty(redirectAllTo)) {
+            // little trick here so we don't loop on ourself
+            if (httpRequest.getSession().getAttribute("_FORCE_REDIRECT_") == null) {
+                httpRequest.getSession().setAttribute("_FORCE_REDIRECT_", "true");
+                Debug.logWarning("Redirecting user to: " + redirectAllTo, module);
+
+                if (!redirectAllTo.toLowerCase().startsWith("http")) {
+                    redirectAllTo = httpRequest.getContextPath() + redirectAllTo;
+                }
+                httpResponse.sendRedirect(redirectAllTo);
+                return;
+            } else {
+                httpRequest.getSession().removeAttribute("_FORCE_REDIRECT_");
+                chain.doFilter(httpRequest, httpResponse);
+                return;
+            }
+        }
+
+        // test to see if we have come through the control servlet already, if not do the processing
+        String requestPath = null;
+        String contextUri = null;
+        if (httpRequest.getAttribute(ContextFilter.FORWARDED_FROM_SERVLET) == null) {
+            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(UrlRegexpConfigUtil.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();
+    }
+
+}

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlRegexpContextFilter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlUtil.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlUtil.java?rev=1535171&view=auto
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlUtil.java (added)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlUtil.java Wed Oct 23 20:48:36 2013
@@ -0,0 +1,15 @@
+package org.ofbiz.product.category;
+
+import org.ofbiz.base.util.UtilValidate;
+
+public class UrlUtil {
+    public static String replaceSpecialCharsUrl(String url) {
+        if (UtilValidate.isEmpty(url)) {
+            url = "";
+        }
+        for (String characterPattern : UrlRegexpConfigUtil.getNameFilters().keySet()) {
+            url = url.replaceAll(characterPattern, UrlRegexpConfigUtil.getNameFilters().get(characterPattern));
+        }
+        return url;
+    }
+}

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlUtil.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/UrlUtil.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java?rev=1535171&view=auto
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java (added)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java Wed Oct 23 20:48:36 2013
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * 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.Locale;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+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.product.category.CatalogUrlFilter;
+import org.ofbiz.product.category.CategoryContentWrapper;
+import org.ofbiz.product.category.UrlRegexpConfigUtil;
+import org.ofbiz.product.product.ProductContentWrapper;
+import org.ofbiz.service.LocalDispatcher;
+import org.ofbiz.webapp.control.RequestHandler;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.BeanModel;
+import freemarker.ext.beans.NumberModel;
+import freemarker.ext.beans.StringModel;
+import freemarker.template.SimpleNumber;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+
+public class CatalogAltUrlSeoTransform implements TemplateTransformModel {
+    public final static String module = CatalogUrlSeoTransform.class.getName();
+
+    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();
+        } else if (o instanceof SimpleNumber) {
+            return ((SimpleNumber) o).getAsNumber().toString();
+        } else if (o instanceof NumberModel) {
+            return ((NumberModel) o).getAsNumber().toString();
+        }
+        return null;
+    }
+
+    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;
+        }
+    }
+
+    @Override
+    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) {
+
+            public void write(char[] cbuf, int off, int len) throws IOException {
+                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");
+                    String previousCategoryId = getStringArg(args, "previousCategoryId");
+                    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");
+                    String viewSort = getStringArg(args, "viewSort");
+                    String searchString = getStringArg(args, "searchString");
+                    if (req != null) {
+                        HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
+                        StringBuilder newURL = new StringBuilder();
+                        if (UtilValidate.isNotEmpty(productId)) {
+                            if (UrlRegexpConfigUtil.isCategoryUrlEnabled(request.getContextPath())) {
+                                url = CatalogUrlSeoTransform.makeProductUrl(request, productId, productCategoryId, previousCategoryId);
+                            } else {
+                                url = CatalogUrlFilter.makeProductUrl(request, previousCategoryId, productCategoryId, productId);
+                            }
+                        } else {
+                            if (UrlRegexpConfigUtil.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) {
+                            String serverRootUrl = RequestHandler.getDefaultServerRootUrl(request, secure);
+                            newURL.append(serverRootUrl);
+                        }
+                        newURL.append(url);
+                        out.write(newURL.toString());
+                    } else if (prefix != null) {
+                        Delegator delegator = FreeMarkerWorker.getWrappedObject("delegator", env);
+                        LocalDispatcher dispatcher = FreeMarkerWorker.getWrappedObject("dispatcher", env);
+                        Locale locale = (Locale) args.get("locale");
+                        if (UtilValidate.isNotEmpty(productId)) {
+                            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 = 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);
+                        }
+                        out.write(url.toString());
+                    } else {
+                        out.write(buf.toString());
+                    }
+                } catch (TemplateModelException e) {
+                    throw new IOException(e.getMessage());
+                } catch (GenericEntityException e) {
+                    throw new IOException(e.getMessage());
+                }
+            }
+        };
+    }
+}

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogAltUrlSeoTransform.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java?rev=1535171&view=auto
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java (added)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java Wed Oct 23 20:48:36 2013
@@ -0,0 +1,668 @@
+/*******************************************************************************
+ * 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.UrlRegexpConfigUtil;
+import org.ofbiz.product.category.UrlUtil;
+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> m_categoryNameIdMap = null;
+    private static Map<String, String> m_categoryIdNameMap = null;
+    private static boolean m_categoryMapInitialed = false;
+    private static final String m_asciiRegexp = "^[0-9-_a-zA-Z]*$";
+    private static Perl5Compiler m_perlCompiler = new Perl5Compiler();
+    private static Pattern m_asciiPattern = null;
+    public static final String URL_HYPHEN = "-";
+
+    static {
+        if (!UrlRegexpConfigUtil.isInitialed()) {
+            UrlRegexpConfigUtil.init();
+        }
+        try {
+            m_asciiPattern = m_perlCompiler.compile(m_asciiRegexp, Perl5Compiler.DEFAULT_MASK);
+        } catch (MalformedPatternException e1) {
+            // do nothing
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    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
+    @SuppressWarnings("unchecked")
+    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 (UrlRegexpConfigUtil.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 m_categoryMapInitialed;
+    }
+    
+    /**
+     * Get the category name/id map.
+     *
+     * @return the category name/id map
+     */
+    public static Map<String, String> getCategoryNameIdMap() {
+        return m_categoryNameIdMap;
+    }
+    
+    /**
+     * Get the category id/name map.
+     *
+     * @return the category id/name map
+     */
+    public static Map<String, String> getCategoryIdNameMap() {
+        return m_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 (UrlRegexpConfigUtil.checkCategoryUrl()) {
+            m_categoryNameIdMap = FastMap.newInstance();
+            m_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 = UrlUtil.replaceSpecialCharsUrl(categoryName.trim().toLowerCase());
+                        if (matcher.matches(categoryName, m_asciiPattern)) {
+                            categoryIdName = categoryName.toLowerCase().replaceAll(" ", URL_HYPHEN);
+                            categoryNameId = categoryIdName + URL_HYPHEN + categoryId.trim().toLowerCase().replaceAll(" ", URL_HYPHEN);
+                        } else {
+                            categoryIdName = categoryId.trim().toLowerCase().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 = UrlUtil.replaceSpecialCharsUrl(alternativeUrl.toString());
+                            categoryNameId = categoryIdName + URL_HYPHEN + categoryId.trim().toLowerCase().replaceAll(" ", URL_HYPHEN);
+                        } else {
+                            categoryNameId = categoryId.trim().toLowerCase().replaceAll(" ", URL_HYPHEN);
+                            categoryIdName = categoryNameId;
+                        }
+                    }
+                    if (m_categoryNameIdMap.containsKey(categoryNameId)) {
+                        categoryNameId = categoryId.trim().toLowerCase().replaceAll(" ", URL_HYPHEN);
+                        categoryIdName = categoryNameId;
+                    }
+                    if (!matcher.matches(categoryNameId, m_asciiPattern) || m_categoryNameIdMap.containsKey(categoryNameId)) {
+                        continue;
+                    }
+                    m_categoryNameIdMap.put(categoryNameId, categoryId);
+                    m_categoryIdNameMap.put(categoryId, categoryIdName);
+                }
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+            }
+        }
+        m_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 (!UrlRegexpConfigUtil.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 (UrlRegexpConfigUtil.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 = UrlUtil.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 = UrlUtil.replaceSpecialCharsUrl(alternativeUrl.toString());
+                        if (UtilValidate.isNotEmpty(productName)) {
+                            urlBuilder.append(productName + URL_HYPHEN);
+                        }
+                    }
+                }
+            }
+            try {
+                UrlRegexpConfigUtil.addSpecialProductId(productId);
+                urlBuilder.append(productId.toLowerCase());
+            } catch (Exception e) {
+                urlBuilder.append(productId);
+            }
+        }
+        
+        if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(UrlRegexpConfigUtil.getCategoryUrlSuffix())) {
+            urlBuilder.append(UrlRegexpConfigUtil.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().toLowerCase().replaceAll(" ", URL_HYPHEN));
+                    } else {
+                        urlBuilder.append(lastCategoryId.trim().toLowerCase().replaceAll(" ", URL_HYPHEN));
+                    }
+                }
+            }
+        }
+
+        if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(UrlRegexpConfigUtil.getCategoryUrlSuffix())) {
+            urlBuilder.append(UrlRegexpConfigUtil.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 (!UrlRegexpConfigUtil.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 (!UrlRegexpConfigUtil.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 (UrlRegexpConfigUtil.isCategoryNameEnabled()) {
+                            String categoryName = CatalogUrlSeoTransform.getCategoryIdNameMap().get(lastCategoryId);
+                            if (UtilValidate.isNotEmpty(categoryName)) {
+                                urlBuilder.append(categoryName + URL_HYPHEN);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (UtilValidate.isNotEmpty(productId)) {
+            if (!UrlRegexpConfigUtil.isCategoryUrlEnabled(contextPath)) {
+                urlBuilder.append("/p_");
+            } else {
+                productName = UrlUtil.replaceSpecialCharsUrl(productName);
+                if (UtilValidate.isNotEmpty(productName)) {
+                    urlBuilder.append(productName + URL_HYPHEN);
+                }
+            }
+            urlBuilder.append(productId);
+        }
+        
+        if (!urlBuilder.toString().endsWith("/") && UtilValidate.isNotEmpty(UrlRegexpConfigUtil.getCategoryUrlSuffix())) {
+            urlBuilder.append(UrlRegexpConfigUtil.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().toLowerCase().replaceAll(" ", URL_HYPHEN);
+            if (UtilValidate.isNotEmpty(name) && matcher.matches(name, m_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 (!UrlRegexpConfigUtil.isCategoryUrlEnabled(contextPath)) {
+            return false;
+        }
+        List<String> pathElements = StringUtil.split(pathInfo, "/");
+        if (UtilValidate.isEmpty(pathElements)) {
+            return false;
+        }
+        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(UrlRegexpConfigUtil.getCategoryUrlSuffix())) {
+                if (lastPathElement.endsWith(UrlRegexpConfigUtil.getCategoryUrlSuffix())) {
+                    lastPathElement = lastPathElement.substring(0, lastPathElement.length() - UrlRegexpConfigUtil.getCategoryUrlSuffix().length());
+                } else {
+                    return false;
+                }
+            }
+            if (UrlRegexpConfigUtil.isCategoryNameEnabled() || pathInfo.startsWith("/" + CatalogUrlServlet.CATEGORY_REQUEST + "/")) {
+                for (String categoryName : m_categoryNameIdMap.keySet()) {
+                    if (lastPathElement.startsWith(categoryName)) {
+                        categoryId = m_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 (UrlRegexpConfigUtil.isSpecialProductId(tempProductId)) {
+                                exprs.add(EntityCondition.makeCondition("productId", EntityOperator.EQUALS, UrlRegexpConfigUtil.getSpecialProductId(tempProductId)));
+                            } else {
+                                exprs.add(EntityCondition.makeCondition("productId", EntityOperator.EQUALS, tempProductId.toUpperCase()));
+                            }
+                            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 (!UrlRegexpConfigUtil.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(UrlRegexpConfigUtil.getCategoryUrlSuffix())) {
+                if (lastPathElement.endsWith(UrlRegexpConfigUtil.getCategoryUrlSuffix())) {
+                    lastPathElement = lastPathElement.substring(0, lastPathElement.length() - UrlRegexpConfigUtil.getCategoryUrlSuffix().length());
+                } else {
+                    return false;
+                }
+            }
+            for (String categoryName : m_categoryNameIdMap.keySet()) {
+                if (lastPathElement.startsWith(categoryName)) {
+                    categoryId = m_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;
+    }
+
+}
\ No newline at end of file

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/CatalogUrlSeoTransform.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java?rev=1535171&view=auto
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java (added)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java Wed Oct 23 20:48:36 2013
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * 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.Locale;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+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.product.category.CatalogUrlFilter;
+import org.ofbiz.product.category.CategoryContentWrapper;
+import org.ofbiz.product.product.ProductContentWrapper;
+import org.ofbiz.service.LocalDispatcher;
+import org.ofbiz.webapp.control.RequestHandler;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.BeanModel;
+import freemarker.ext.beans.NumberModel;
+import freemarker.ext.beans.StringModel;
+import freemarker.template.SimpleNumber;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+
+public class OfbizCatalogAltUrlTransform implements TemplateTransformModel {
+    public final static String module = OfbizCatalogUrlTransform.class.getName();
+
+    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();
+        } else if (o instanceof SimpleNumber) {
+            return ((SimpleNumber) o).getAsNumber().toString();
+        } else if (o instanceof NumberModel) {
+            return ((NumberModel) o).getAsNumber().toString();
+        }
+        return null;
+    }
+
+    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;
+        }
+    }
+
+    @Override
+    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();
+                    BeanModel req = (BeanModel) env.getVariable("request");
+                    String previousCategoryId = getStringArg(args, "previousCategoryId");
+                    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");
+                    String viewSort = getStringArg(args, "viewSort");
+                    String searchString = getStringArg(args, "searchString");
+                    if (req != null) {
+                        HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
+                        StringBuilder newURL = new StringBuilder();
+                        if (UtilValidate.isNotEmpty(productId)) {
+                            url = CatalogUrlFilter.makeProductUrl(request, previousCategoryId, productCategoryId, productId);
+                        } else {
+                            url = CatalogUrlFilter.makeCategoryUrl(request, previousCategoryId, productCategoryId, productId, viewSize, viewIndex, viewSort, searchString);
+                        }
+                        // make the link
+                        if (fullPath){
+                            String serverRootUrl = RequestHandler.getDefaultServerRootUrl(request, secure);
+                            newURL.append(serverRootUrl);
+                        }
+                        newURL.append(url);
+                        out.write(newURL.toString());
+                    } else if (prefix != null) {
+                        Delegator delegator = FreeMarkerWorker.getWrappedObject("delegator", env);
+                        LocalDispatcher dispatcher = FreeMarkerWorker.getWrappedObject("dispatcher", env);
+                        Locale locale = (Locale) args.get("locale");
+                        if (UtilValidate.isNotEmpty(productId)) {
+                            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 = 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);
+                        }
+                        out.write(url.toString());
+                    } else {
+                        out.write(buf.toString());
+                    }
+                } catch (TemplateModelException e) {
+                    throw new IOException(e.getMessage());
+                } catch (GenericEntityException e) {
+                    throw new IOException(e.getMessage());
+                }
+            }
+        };
+    }
+}

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogAltUrlTransform.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java?rev=1535171&view=auto
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java (added)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java Wed Oct 23 20:48:36 2013
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * 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.Map;
+
+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;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+
+public class OfbizCatalogUrlTransform implements TemplateTransformModel {
+    public final static String module = OfbizCatalogUrlTransform.class.getName();
+    
+    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();
+                        String catalogUrl = CatalogUrlServlet.makeCatalogUrl(request, productId, currentCategoryId, previousCategoryId);
+                        out.write(catalogUrl);
+                    }
+                } catch (TemplateModelException e) {
+                    throw new IOException(e.getMessage());
+                }
+            }
+        };
+    }
+}
\ No newline at end of file

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/OfbizCatalogUrlTransform.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java?rev=1535171&view=auto
==============================================================================
--- ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java (added)
+++ ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java Wed Oct 23 20:48:36 2013
@@ -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 && SeoConfigUtil.isDebugEnabled()) {
+                Debug.logInfo("Can NOT find a seo transform pattern for this url: " + url, module);
+            }
+        }
+        return url;
+    }
+
+    static {
+        if (!SeoConfigUtil.isInitialed()) {
+            SeoConfigUtil.init();
+        }
+    }
+}

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/branches/OFBIZ-5312-ofbiz-ecommerce-seo-2013-10-23/applications/product/src/org/ofbiz/product/category/ftl/SeoTransform.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain