Skip to content

Commit 9301202

Browse files
committed
Add human-readable URLs for countries' pages.
Fix GH #50
1 parent 0ccfd7e commit 9301202

25 files changed

+243
-27
lines changed

src/main/java/ru/mystamps/web/Url.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public final class Url {
5555
public static final String INFO_CATEGORY_PAGE = "/category/{id}";
5656

5757
public static final String ADD_COUNTRY_PAGE = "/country/add";
58-
public static final String INFO_COUNTRY_PAGE = "/country/{id}";
58+
public static final String INFO_COUNTRY_PAGE = "/country/{id}/{slug}";
5959

6060
public static final String INFO_COLLECTION_PAGE = "/collection/{id}";
6161

@@ -81,6 +81,7 @@ private Url() {
8181
public static Map<String, String> asMap() {
8282
// There is not all urls but only those which used on views
8383
Map<String, String> map = new HashMap<>();
84+
map.put("PUBLIC_URL", PUBLIC_URL);
8485
map.put("AUTHENTICATION_PAGE", AUTHENTICATION_PAGE);
8586
map.put("LOGIN_PAGE", LOGIN_PAGE);
8687
map.put("LOGOUT_PAGE", LOGOUT_PAGE);

src/main/java/ru/mystamps/web/controller/CountryController.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public String processInput(@Valid AddCountryForm form, BindingResult result, Use
7171
Country country = countryService.add(form, currentUser);
7272

7373
String dstUrl = UriComponentsBuilder.fromUriString(Url.INFO_COUNTRY_PAGE)
74-
.buildAndExpand(country.getId())
74+
.buildAndExpand(country.getId(), country.getSlug())
7575
.toString();
7676

7777
return "redirect:" + dstUrl;

src/main/java/ru/mystamps/web/controller/SeriesController.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import ru.mystamps.web.service.CountryService;
5757
import ru.mystamps.web.service.SeriesService;
5858
import ru.mystamps.web.service.dto.EntityInfoDto;
59+
import ru.mystamps.web.service.dto.SelectEntityDto;
5960
import ru.mystamps.web.support.spring.security.SecurityContextUtils;
6061
import ru.mystamps.web.util.CatalogUtils;
6162
import ru.mystamps.web.util.LocaleUtils;
@@ -103,7 +104,7 @@ public Iterable<EntityInfoDto> getCategories(Locale userLocale) {
103104
}
104105

105106
@ModelAttribute("countries")
106-
public Iterable<EntityInfoDto> getCountries(Locale userLocale) {
107+
public Iterable<SelectEntityDto> getCountries(Locale userLocale) {
107108
String lang = LocaleUtils.getLanguageOrNull(userLocale);
108109
return countryService.findAll(lang);
109110
}

src/main/java/ru/mystamps/web/dao/CountryDao.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@
2222
import org.springframework.data.repository.query.Param;
2323

2424
import ru.mystamps.web.entity.Country;
25-
import ru.mystamps.web.service.dto.EntityInfoDto;
25+
import ru.mystamps.web.service.dto.SelectEntityDto;
2626

2727
public interface CountryDao extends PagingAndSortingRepository<Country, Integer> {
2828
int countByName(String name);
2929
int countByNameRu(String name);
3030

3131
@Query(
32-
"SELECT NEW ru.mystamps.web.service.dto.EntityInfoDto("
32+
"SELECT NEW ru.mystamps.web.service.dto.SelectEntityDto("
3333
+ "c.id, "
3434
+ "CASE WHEN (:lang = 'ru') THEN c.nameRu ELSE c.name END"
3535
+ ") "
3636
+ "FROM Country c "
3737
+ "ORDER BY c.name"
3838
)
39-
Iterable<EntityInfoDto> findAllAsSelectEntries(@Param("lang") String lang);
39+
Iterable<SelectEntityDto> findAllAsSelectEntries(@Param("lang") String lang);
4040
}

src/main/java/ru/mystamps/web/dao/SeriesDao.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public interface SeriesDao extends CrudRepository<Series, Integer> {
4747
"SELECT NEW ru.mystamps.web.service.dto.SeriesInfoDto("
4848
+ "s.id, "
4949
+ "cat.id, CASE WHEN (:lang = 'ru') THEN cat.nameRu ELSE cat.name END, "
50-
+ "c.id, CASE WHEN (:lang = 'ru') THEN c.nameRu ELSE c.name END, "
50+
+ "c.id, c.slug, CASE WHEN (:lang = 'ru') THEN c.nameRu ELSE c.name END, "
5151
+ "s.releaseDay, "
5252
+ "s.releaseMonth, "
5353
+ "s.releaseYear, "
@@ -68,7 +68,7 @@ Iterable<SeriesInfoDto> findByAsSeriesInfo(
6868
"SELECT NEW ru.mystamps.web.service.dto.SeriesInfoDto("
6969
+ "s.id, "
7070
+ "cat.id, CASE WHEN (:lang = 'ru') THEN cat.nameRu ELSE cat.name END, "
71-
+ "c.id, CASE WHEN (:lang = 'ru') THEN c.nameRu ELSE c.name END, "
71+
+ "c.id, c.slug, CASE WHEN (:lang = 'ru') THEN c.nameRu ELSE c.name END, "
7272
+ "s.releaseDay, "
7373
+ "s.releaseMonth, "
7474
+ "s.releaseYear, "
@@ -89,7 +89,7 @@ Iterable<SeriesInfoDto> findByAsSeriesInfo(
8989
"SELECT NEW ru.mystamps.web.service.dto.SeriesInfoDto("
9090
+ "s.id, "
9191
+ "cat.id, CASE WHEN (:lang = 'ru') THEN cat.nameRu ELSE cat.name END, "
92-
+ "c.id, CASE WHEN (:lang = 'ru') THEN c.nameRu ELSE c.name END, "
92+
+ "c.id, c.slug, CASE WHEN (:lang = 'ru') THEN c.nameRu ELSE c.name END, "
9393
+ "s.releaseDay, "
9494
+ "s.releaseMonth, "
9595
+ "s.releaseYear, "

src/main/java/ru/mystamps/web/dao/impl/SeriesInfoDtoRowMapper.java

+2
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ public SeriesInfoDto mapRow(ResultSet resultSet, int i) throws SQLException {
3737
Integer categoryId = resultSet.getInt("category_id");
3838
String categoryName = resultSet.getString("category_name");
3939
Integer countryId = JdbcUtils.getInteger(resultSet, "country_id");
40+
String countrySlug = resultSet.getString("country_slug");
4041
String countryName = resultSet.getString("country_name");
4142

4243
return new SeriesInfoDto(
4344
seriesId,
4445
categoryId,
4546
categoryName,
4647
countryId,
48+
countrySlug,
4749
countryName,
4850
releaseDay,
4951
releaseMonth,

src/main/java/ru/mystamps/web/entity/Country.java

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
public class Country implements LocalizedEntity {
4141

4242
public static final int NAME_LENGTH = 50;
43+
public static final int SLUG_LENGTH = NAME_LENGTH;
4344

4445
@Id
4546
@GeneratedValue
@@ -51,6 +52,9 @@ public class Country implements LocalizedEntity {
5152
@Column(name = "name_ru", length = NAME_LENGTH, nullable = false, unique = true)
5253
private String nameRu;
5354

55+
@Column(length = SLUG_LENGTH, nullable = false)
56+
private String slug;
57+
5458
@Embedded
5559
private MetaInfo metaInfo; // NOPMD
5660

src/main/java/ru/mystamps/web/service/CountryService.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
import ru.mystamps.web.entity.Country;
2222
import ru.mystamps.web.entity.User;
2323
import ru.mystamps.web.service.dto.AddCountryDto;
24-
import ru.mystamps.web.service.dto.EntityInfoDto;
24+
import ru.mystamps.web.service.dto.SelectEntityDto;
2525

2626
public interface CountryService {
2727
Country add(AddCountryDto dto, User user);
28-
Iterable<EntityInfoDto> findAll(String lang);
28+
Iterable<SelectEntityDto> findAll(String lang);
2929
long countAll();
3030
long countCountriesOf(Collection collection);
3131
int countByName(String name);

src/main/java/ru/mystamps/web/service/CountryServiceImpl.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
import ru.mystamps.web.entity.User;
3636
import ru.mystamps.web.dao.CountryDao;
3737
import ru.mystamps.web.service.dto.AddCountryDto;
38-
import ru.mystamps.web.service.dto.EntityInfoDto;
38+
import ru.mystamps.web.service.dto.SelectEntityDto;
39+
import ru.mystamps.web.util.SlugUtils;
3940

4041
@RequiredArgsConstructor
4142
public class CountryServiceImpl implements CountryService {
@@ -57,6 +58,10 @@ public Country add(AddCountryDto dto, User user) {
5758
country.setName(dto.getName());
5859
country.setNameRu(dto.getNameRu());
5960

61+
String slug = SlugUtils.slugify(dto.getName());
62+
Validate.isTrue(slug != null, "Slug for string '%s' is null", dto.getName());
63+
country.setSlug(slug);
64+
6065
Date now = new Date();
6166
country.getMetaInfo().setCreatedAt(now);
6267
country.getMetaInfo().setUpdatedAt(now);
@@ -72,7 +77,7 @@ public Country add(AddCountryDto dto, User user) {
7277

7378
@Override
7479
@Transactional(readOnly = true)
75-
public Iterable<EntityInfoDto> findAll(String lang) {
80+
public Iterable<SelectEntityDto> findAll(String lang) {
7681
return countryDao.findAllAsSelectEntries(lang);
7782
}
7883

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (C) 2009-2014 Slava Semushin <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.service.dto;
19+
20+
import lombok.Getter;
21+
import lombok.RequiredArgsConstructor;
22+
import lombok.ToString;
23+
24+
@Getter
25+
@ToString
26+
@RequiredArgsConstructor
27+
public class LinkEntityDto {
28+
private final Integer id;
29+
private final String slug;
30+
private final String name;
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (C) 2009-2014 Slava Semushin <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.service.dto;
19+
20+
import lombok.Getter;
21+
import lombok.RequiredArgsConstructor;
22+
import lombok.ToString;
23+
24+
@Getter
25+
@ToString
26+
@RequiredArgsConstructor
27+
public class SelectEntityDto {
28+
private final Integer id;
29+
private final String name;
30+
}

src/main/java/ru/mystamps/web/service/dto/SeriesInfoDto.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class SeriesInfoDto {
2929
private final EntityInfoDto category;
3030

3131
@SuppressWarnings("PMD.SingularField")
32-
private final EntityInfoDto country;
32+
private final LinkEntityDto country;
3333

3434
@SuppressWarnings("PMD.SingularField")
3535
private final Integer releaseDay;
@@ -50,13 +50,13 @@ public class SeriesInfoDto {
5050
public SeriesInfoDto(
5151
Integer id,
5252
Integer categoryId, String categoryName,
53-
Integer countryId, String countryName,
53+
Integer countryId, String countrySlug, String countryName,
5454
Integer releaseDay, Integer releaseMonth, Integer releaseYear,
5555
Integer quantity,
5656
Boolean perforated) {
5757
this.id = id;
5858
this.category = new EntityInfoDto(categoryId, categoryName);
59-
this.country = new EntityInfoDto(countryId, countryName);
59+
this.country = new LinkEntityDto(countryId, countrySlug, countryName);
6060
this.releaseDay = releaseDay;
6161
this.releaseMonth = releaseMonth;
6262
this.releaseYear = releaseYear;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (C) 2009-2014 Slava Semushin <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.util;
19+
20+
import java.util.Locale;
21+
22+
import org.apache.commons.lang3.Validate;
23+
24+
public final class SlugUtils {
25+
26+
private SlugUtils() {
27+
}
28+
29+
public static String slugify(String text) {
30+
Validate.isTrue(text != null, "Text must be non null");
31+
32+
return text.toLowerCase(Locale.ENGLISH)
33+
// replace all characters except letters and digits to hyphen
34+
.replaceAll("[^\\p{Alnum}]", "-")
35+
// replace multiple hyphens by one
36+
.replaceAll("-{2,}", "-")
37+
// remove leading hyphen
38+
.replaceAll("^-", "")
39+
// remove ending hyphen
40+
.replaceAll("-$", "");
41+
}
42+
43+
}

src/main/resources/liquibase/version/0.3.xml

+1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
<include file="0.3/2014-06-12--ru_country_name.xml" relativeToChangelogFile="true" />
1111
<include file="0.3/2014-08-16--users_activation_lang.xml" relativeToChangelogFile="true" />
1212
<include file="0.3/2014-08-30--collections.xml" relativeToChangelogFile="true" />
13+
<include file="0.3/2014-09-17--country_slug.xml" relativeToChangelogFile="true" />
1314

1415
</databaseChangeLog>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<databaseChangeLog
3+
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
6+
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
7+
8+
<changeSet id="add-slug-column-to-countries-table" author="php-coder" context="scheme">
9+
<comment>Adds slug column to countries table</comment>
10+
11+
<addColumn tableName="countries">
12+
<column name="slug" type="VARCHAR(50)" afterColumn="name_ru" />
13+
</addColumn>
14+
15+
</changeSet>
16+
17+
<changeSet id="update-countries-slug" author="php-coder" context="test-data,prod-data">
18+
<comment>Sets value of slug field to value of name field in lower case</comment>
19+
20+
<update tableName="countries">
21+
<column name="slug" valueComputed="LOWER(name)" />
22+
</update>
23+
24+
</changeSet>
25+
26+
<changeSet id="make-slug-field-not-nullable" author="php-coder" context="scheme">
27+
<comment>Marks countries.slug field as NOT NULL</comment>
28+
29+
<addNotNullConstraint tableName="countries" columnName="slug" columnDataType="VARCHAR(50)" />
30+
31+
</changeSet>
32+
33+
</databaseChangeLog>

src/main/resources/sql/series_dao_queries.properties

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ series.find_last_added_sql = \
88
, cat.id AS category_id \
99
, cat.name AS category_name \
1010
, count.id AS country_id \
11+
, count.slug AS country_slug \
1112
, count.name AS country_name \
1213
FROM series s \
1314
JOIN categories cat \
@@ -27,6 +28,7 @@ series.find_last_added_ru_sql = \
2728
, cat.id AS category_id \
2829
, cat.name_ru AS category_name \
2930
, count.id AS country_id \
31+
, count.slug AS country_slug \
3032
, count.name_ru AS country_name \
3133
FROM series s \
3234
JOIN categories cat \

src/main/webapp/WEB-INF/views/category/info.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ <h3 th:text="${category.getLocalizedName(#locale)}">
5959
<ul th:if="${not #lists.isEmpty(seriesOfCategory)}" th:remove="all-but-first">
6060
<li th:each="series: ${seriesOfCategory}">
6161
<span th:if="${series.country.id != null}" th:remove="tag">
62-
<a href="../country/info.html" th:href="@{${INFO_COUNTRY_PAGE}(id=${series.country.id})}" th:text="${series.country.name}">Italy</a>&nbsp;&raquo;
62+
<a href="../country/info.html" th:href="@{${INFO_COUNTRY_PAGE}(id=${series.country.id},slug=${series.country.slug})}" th:text="${series.country.name}">Italy</a>&nbsp;&raquo;
6363
</span>
6464

6565
<a href="../series/info.html" th:href="@{${INFO_SERIES_PAGE}(id=${series.id})}">

src/main/webapp/WEB-INF/views/collection/info.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ <h3 th:text="#{t_collection_of(${ownerName})}">
8686
</span>
8787

8888
<span th:if="${series.country.id != null}" th:remove="tag">
89-
<a href="../country/info.html" th:href="@{${INFO_COUNTRY_PAGE}(id=${series.country.id})}" th:text="${series.country.name}">Italy</a>&nbsp;&raquo;
89+
<a href="../country/info.html" th:href="@{${INFO_COUNTRY_PAGE}(id=${series.country.id},slug=${series.country.slug})}" th:text="${series.country.name}">Italy</a>&nbsp;&raquo;
9090
</span>
9191

9292
<a href="../series/info.html" th:href="@{${INFO_SERIES_PAGE}(id=${series.id})}">

src/main/webapp/WEB-INF/views/country/info.html

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<link rel="stylesheet" href="http://yandex.st/bootstrap/2.3.1/css/bootstrap.min.css" th:href="${BOOTSTRAP_CSS}" />
1111
<link rel="stylesheet" href="http://yandex.st/bootstrap/2.3.1/css/bootstrap-responsive.min.css" th:href="${BOOTSTRAP_RESPONSIVE_CSS}" />
1212
<link rel="stylesheet" href="../../static/styles/main.css" th:href="${MAIN_CSS}" />
13+
<link rel="canonical" href="" th:href="@{${PUBLIC_URL} + ${INFO_COUNTRY_PAGE}(id=${country.id},slug=${country.slug})}" />
1314
</head>
1415
<body lang="en" th:lang="${#locale.language == 'ru' ? 'ru' : 'en'}">
1516
<div class="row-fluid">

src/main/webapp/WEB-INF/views/series/info.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
</dt>
7777
<dd id="country_name">
7878
<a href="../country/info.html"
79-
th:href="@{${INFO_COUNTRY_PAGE}(id=${series.country.id})}"
79+
th:href="@{${INFO_COUNTRY_PAGE}(id=${series.country.id},slug=${series.country.slug})}"
8080
th:text="${series.country.getLocalizedName(#locale)}">
8181
Italy
8282
</a>

0 commit comments

Comments
 (0)