Skip to content

Commit e4e6273

Browse files
committed
For QA#878, provide services to build a relative URL for a product or category.
Consider a product whose URL is "/hot-sauces/green-ghost" If the page being served is "/clearance", then #blc.relativeURL(product) would produce `/clearance/green-ghost?productId=x` This is useful in correlation with dynamic breadcrumbs. See BroadleafCommerce/QA#294 The system only appends the productId=x parameter if the `catalogUriService.appendIdToRelativeURI` property is `true`
1 parent b231986 commit e4e6273

File tree

6 files changed

+502
-1
lines changed

6 files changed

+502
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* #%L
3+
* BroadleafCommerce CMS Module
4+
* %%
5+
* Copyright (C) 2009 - 2013 Broadleaf Commerce
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package org.broadleafcommerce.core.web.processor;
21+
22+
import org.broadleafcommerce.common.web.BroadleafRequestContext;
23+
import org.broadleafcommerce.core.catalog.domain.Category;
24+
import org.broadleafcommerce.core.catalog.domain.Product;
25+
import org.broadleafcommerce.core.catalog.service.CatalogURLService;
26+
import org.thymeleaf.Arguments;
27+
import org.thymeleaf.dom.Element;
28+
import org.thymeleaf.processor.attr.AbstractAttributeModifierAttrProcessor;
29+
import org.thymeleaf.standard.expression.Expression;
30+
import org.thymeleaf.standard.expression.StandardExpressions;
31+
32+
import java.util.HashMap;
33+
import java.util.Map;
34+
35+
import javax.annotation.Resource;
36+
import javax.servlet.http.HttpServletRequest;
37+
38+
/**
39+
* For use with category and product entities. Creates a relative URL using the
40+
* current URI appended with the url-key (or last fragment of the url).
41+
*
42+
* Takes in a category or product object as a parameter.
43+
*
44+
* Uses the current request for the baseURI.
45+
*
46+
* This implementation will also a categoryId or productId to the end of the URL it generates.
47+
*
48+
* @author bpolster
49+
*/
50+
public class CatalogRelativeHrefProcessor extends AbstractAttributeModifierAttrProcessor {
51+
52+
private static final String RHREF = "rhref";
53+
private static final String HREF = "href";
54+
55+
@Resource(name = "blCatalogURLService")
56+
protected CatalogURLService catalogURLService;
57+
58+
public CatalogRelativeHrefProcessor() {
59+
super(RHREF);
60+
}
61+
62+
@Override
63+
protected Map<String, String> getModifiedAttributeValues(Arguments arguments, Element element, String attributeName) {
64+
Expression expression = (Expression) StandardExpressions.getExpressionParser(arguments.getConfiguration())
65+
.parseExpression(arguments.getConfiguration(), arguments, element.getAttributeValue(attributeName));
66+
HttpServletRequest request = BroadleafRequestContext.getBroadleafRequestContext().getRequest();
67+
68+
String relativeHref = buildRelativeHref(expression, arguments, request);
69+
70+
Map<String, String> attrs = new HashMap<String, String>();
71+
attrs.put(HREF, relativeHref);
72+
return attrs;
73+
}
74+
75+
protected String buildRelativeHref(Expression expression, Arguments arguments, HttpServletRequest request) {
76+
Object result = expression.execute(arguments.getConfiguration(), arguments);
77+
String currentUrl = request.getRequestURI();
78+
79+
if (request.getQueryString() != null) {
80+
currentUrl = currentUrl + "?" + request.getQueryString();
81+
}
82+
83+
if (result instanceof Product) {
84+
return catalogURLService.buildRelativeProductURL(currentUrl, (Product) result);
85+
} else if (result instanceof Category) {
86+
return catalogURLService.buildRelativeCategoryURL(currentUrl, (Category) result);
87+
}
88+
return "";
89+
}
90+
91+
@Override
92+
protected ModificationType getModificationType(Arguments arguments, Element element, String attributeName, String newAttributeName) {
93+
return ModificationType.SUBSTITUTION;
94+
}
95+
96+
@Override
97+
protected boolean removeAttributeIfEmpty(Arguments arguments, Element element, String attributeName, String newAttributeName) {
98+
return true;
99+
}
100+
101+
@Override
102+
protected boolean recomputeProcessorsAfterExecution(Arguments arguments, Element element, String attributeName) {
103+
return false;
104+
}
105+
106+
@Override
107+
public int getPrecedence() {
108+
return 0;
109+
}
110+
}

core/broadleaf-framework-web/src/main/resources/bl-framework-web-applicationContext.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
<bean id="blOnePageCheckoutProcessor" class="org.broadleafcommerce.core.web.processor.OnePageCheckoutProcessor"/>
6969
<bean id="blUncacheableDataProcessor" class="org.broadleafcommerce.core.web.processor.UncacheableDataProcessor"/>
7070
<bean id="blGoogleUniversalAnalyticsProcessor" class="org.broadleafcommerce.core.web.processor.GoogleUniversalAnalyticsProcessor" />
71+
<bean id="blCatalogRelativeHrefProcessor" class="org.broadleafcommerce.core.web.processor.CatalogRelativeHrefProcessor" />
7172

7273
<bean id="blDialectFrameworkProcessors" class="org.springframework.beans.factory.config.SetFactoryBean">
7374
<property name="sourceSet">
@@ -95,6 +96,8 @@
9596
<ref bean="blCreditCardTypesProcessor" />
9697
<ref bean="blUncacheableDataProcessor" />
9798
<ref bean="blGoogleUniversalAnalyticsProcessor" />
99+
<ref bean="blCatalogRelativeHrefProcessor" />
100+
98101
</set>
99102
</property>
100103
</bean>
@@ -112,9 +115,10 @@
112115
<property name="sourceList">
113116
<list>
114117
<bean class="org.broadleafcommerce.common.web.expression.NullBroadleafVariableExpression" />
115-
<bean class="org.broadleafcommerce.common.web.expression.BRCVariableExpression" />
118+
<bean class="org.broadleafcommerce.common.web.expression.BRCVariableExpression" />
116119
<bean class="org.broadleafcommerce.common.web.expression.PropertiesVariableExpression" />
117120
<bean class="org.broadleafcommerce.common.web.payment.expression.PaymentGatewayFieldVariableExpression"/>
121+
<bean class="org.broadleafcommerce.core.web.expression.BLCVariableExpression" />
118122
<bean class="org.broadleafcommerce.profile.web.core.expression.CustomerVariableExpression"/>
119123
</list>
120124
</property>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* #%L
3+
* BroadleafCommerce Framework
4+
* %%
5+
* Copyright (C) 2009 - 2013 Broadleaf Commerce
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package org.broadleafcommerce.core.catalog.service;
21+
22+
import org.broadleafcommerce.core.catalog.domain.Category;
23+
import org.broadleafcommerce.core.catalog.domain.Product;
24+
25+
/**
26+
* This service provides some URL manipulation capabilities. Initially provided to support the creation of
27+
* relative URLs and Breadcrumb requirements.
28+
*
29+
* @author bpolster
30+
* @see org.broadleafcommerce.core.web.processor.CatalogRelativeHrefProcessor
31+
*/
32+
public interface CatalogURLService {
33+
34+
/**
35+
* Provides relative URLs. This is useful for cases where a site wants to
36+
* build a dynamic URL to get to a product or category where multiple navigation paths
37+
* are provided.
38+
*
39+
* For example, consider a product with URL (/equipment/tennis-ball) that is in two categories
40+
* which have the following URLs (/sports and /specials).
41+
*
42+
* For some implementations, it is desirable to have two semantic URLs such as
43+
* "/sports/tennis-ball" and "/specials/tennis-ball".
44+
*
45+
* This method will take the last fragment of the product URL and append it to the
46+
* passed in URL to make a relative URL.
47+
*
48+
* This default implementation of this interface uses two system properties to control
49+
* its behavior.
50+
*
51+
* catalogUriService.appendIdToRelativeURI - If true (default), a query param will be appended to the URL
52+
* with the productId.
53+
*
54+
* catalogUriService.useUrlKey - If true (default is false), the implementation will call the
55+
* ProductImpl.getUrlKey() to obtain the url fragment. If false, it will parse the last part of the
56+
* ProductImpl.getUrl().
57+
*
58+
* Returns the URL as a string including query parameters.
59+
*
60+
* @param currentUrl
61+
* @param product
62+
* @return
63+
*/
64+
String buildRelativeProductURL(String currentUrl, Product product);
65+
66+
/**
67+
* See similar description for {@link #buildRelativeProductURL(String, Product)}
68+
* @param currentUrl
69+
* @param category
70+
* @return
71+
*/
72+
String buildRelativeCategoryURL(String currentUrl, Category category);
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* #%L
3+
* BroadleafCommerce Framework
4+
* %%
5+
* Copyright (C) 2009 - 2013 Broadleaf Commerce
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package org.broadleafcommerce.core.catalog.service;
21+
22+
import org.apache.http.client.utils.URIBuilder;
23+
import org.broadleafcommerce.core.catalog.domain.Category;
24+
import org.broadleafcommerce.core.catalog.domain.Product;
25+
import org.springframework.beans.factory.annotation.Value;
26+
import org.springframework.stereotype.Service;
27+
28+
import com.google.common.base.Optional;
29+
30+
import java.net.URI;
31+
import java.net.URISyntaxException;
32+
33+
@Service("blCatalogURLService")
34+
public class CatalogURLServiceImpl implements CatalogURLService {
35+
36+
@Value("${catalogUriService.appendIdToRelativeURI:true}")
37+
protected boolean appendIdToRelativeURI;
38+
39+
@Value("${catalogUriService.useUrlKey:false}")
40+
protected boolean useUrlKey;
41+
42+
@Value("${catalogUriService.productIdParam:productId}")
43+
protected String productIdParam;
44+
45+
@Value("${catalogUriService.categoryIdParam:categoryId}")
46+
protected String categoryIdParam;
47+
48+
@Override
49+
public String buildRelativeProductURL(String currentUrl, Product product) {
50+
String fragment = getProductUrlFragment(product);
51+
return buildRelativeUrlWithParam(currentUrl, fragment, productIdParam, String.valueOf(product.getId()));
52+
}
53+
54+
@Override
55+
public String buildRelativeCategoryURL(String currentUrl, Category category) {
56+
String fragment = getCategoryUrlFragment(category);
57+
return buildRelativeUrlWithParam(currentUrl, fragment, categoryIdParam, String.valueOf(category.getId()));
58+
}
59+
60+
/**
61+
* Adds the fragment to the end of the path and optionally adds an id param depending upon
62+
* the value of appendIdToRelativeURI.
63+
*/
64+
protected String buildRelativeUrlWithParam(String currentUrl, String fragment, String idParam, String idValue) {
65+
try {
66+
URIBuilder builder = new URIBuilder(currentUrl);
67+
builder.setPath(builder.getPath() + "/" + fragment);
68+
69+
if (appendIdToRelativeURI) {
70+
builder.setParameter(idParam, String.valueOf(idValue));
71+
}
72+
73+
return builder.build().toString();
74+
} catch (URISyntaxException e) {
75+
return currentUrl;
76+
}
77+
}
78+
79+
protected String getProductUrlFragment(Product product) {
80+
if (useUrlKey) {
81+
return product.getUrlKey();
82+
} else {
83+
return getLastFragment(product.getUrl());
84+
}
85+
}
86+
87+
protected String getCategoryUrlFragment(Category category) {
88+
if (useUrlKey) {
89+
return category.getUrlKey();
90+
} else {
91+
return getLastFragment(category.getUrl());
92+
}
93+
}
94+
95+
protected String getLastFragment(String url) {
96+
URI uri = URI.create(url);
97+
String path = Optional.fromNullable(uri.getPath()).or("/");
98+
return path.substring(path.lastIndexOf('/') + 1);
99+
}
100+
}

core/broadleaf-framework/src/main/resources/config/bc/fw/common.properties

+8
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,11 @@ default.payment.gateway.checkout.useGatewayBillingAddress=true
8484
disableThymeleafTemplateCaching=false
8585

8686

87+
# If true, relative URLs will have an id appended (e.g. productId=123)
88+
catalogUriService.appendIdToRelativeURI=true
89+
90+
# If true, the catalogUriService will call product.getUrlKey or category.getUrlKey to get
91+
# the last fragment of the URL instead of parsing the results of the product or category "getURL()" method
92+
catalogUriService.useUrlKey=false
93+
94+

0 commit comments

Comments
 (0)