query = manager.createNamedQuery(namedQuery, Long.class);
199 | return setParameters(query, parameters).getSingleResult().intValue();
200 | }
201 |
202 | /**
203 | * Bind parameters to the typed query
204 | * @param query typed query
205 | * @param parameters Map of parameters to set
206 | * @return typed query with parameters
207 | */
208 | private TypedQuery
setParameters(TypedQuery
query,
209 | Map parameters) {
210 |
211 | for (Entry entry : parameters.entrySet()) {
212 | query.setParameter(entry.getKey(), entry.getValue());
213 | }
214 | return query;
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/views/article-view.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=UTF-8"
2 | pageEncoding="UTF-8"%>
3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
4 | <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
5 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
6 | <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
7 | <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
8 | <%@ taglib prefix="t" tagdir="/WEB-INF/tags"%>
9 |
10 | ${article.title} |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | :
62 |
63 |
64 | |
65 | :
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
120 |
121 |
122 |
123 |
139 |
140 | ">
141 |
180 |
181 |
--------------------------------------------------------------------------------
/src/main/webapp/resources/css/summernote.css:
--------------------------------------------------------------------------------
1 | .note-editor{border:1px solid #a9a9a9}.note-editor .note-dropzone{position:absolute;z-index:1;display:none;color:#87cefa;background-color:white;border:2px dashed #87cefa;opacity:.95;pointer-event:none}.note-editor .note-dropzone .note-dropzone-message{display:table-cell;font-size:28px;font-weight:bold;text-align:center;vertical-align:middle}.note-editor .note-dropzone.hover{color:#098ddf;border:2px dashed #098ddf}.note-editor.dragover .note-dropzone{display:table}.note-editor.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%}.note-editor.fullscreen .note-editable{background-color:white}.note-editor.fullscreen .note-resizebar{display:none}.note-editor.codeview .note-editable{display:none}.note-editor.codeview .note-codable{display:block}.note-editor .note-toolbar{padding-bottom:5px;padding-left:5px;margin:0;background-color:#f5f5f5;border-bottom:1px solid #a9a9a9}.note-editor .note-toolbar>.btn-group{margin-top:5px;margin-right:5px;margin-left:0}.note-editor .note-toolbar .note-table .dropdown-menu{min-width:0;padding:5px}.note-editor .note-toolbar .note-table .dropdown-menu .note-dimension-picker{font-size:18px}.note-editor .note-toolbar .note-table .dropdown-menu .note-dimension-picker .note-dimension-picker-mousecatcher{position:absolute!important;z-index:3;width:10em;height:10em;cursor:pointer}.note-editor .note-toolbar .note-table .dropdown-menu .note-dimension-picker .note-dimension-picker-unhighlighted{position:relative!important;z-index:1;width:5em;height:5em;background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIj4+Pjp6ekKlAqjAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKhmnaJzPAAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC') repeat}.note-editor .note-toolbar .note-table .dropdown-menu .note-dimension-picker .note-dimension-picker-highlighted{position:absolute!important;z-index:2;width:1em;height:1em;background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIjd6vvD2f9LKLW+AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKwNDEVT0AAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC') repeat}.note-editor .note-toolbar .note-style h1,.note-editor .note-toolbar .note-style h2,.note-editor .note-toolbar .note-style h3,.note-editor .note-toolbar .note-style h4,.note-editor .note-toolbar .note-style h5,.note-editor .note-toolbar .note-style h6,.note-editor .note-toolbar .note-style blockquote{margin:0}.note-editor .note-toolbar .note-color .dropdown-toggle{width:20px;padding-left:5px}.note-editor .note-toolbar .note-color .dropdown-menu{min-width:290px}.note-editor .note-toolbar .note-color .dropdown-menu .btn-group{margin:0}.note-editor .note-toolbar .note-color .dropdown-menu .btn-group:first-child{margin:0 5px}.note-editor .note-toolbar .note-color .dropdown-menu .btn-group .note-palette-title{margin:2px 7px;font-size:12px;text-align:center;border-bottom:1px solid #eee}.note-editor .note-toolbar .note-color .dropdown-menu .btn-group .note-color-reset{padding:0 3px;margin:5px;font-size:12px;cursor:pointer;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.note-editor .note-toolbar .note-color .dropdown-menu .btn-group .note-color-reset:hover{background:#eee}.note-editor .note-toolbar .note-para .dropdown-menu{min-width:153px;padding:5px}.note-editor .note-toolbar .note-para li:first-child{margin-bottom:5px}.note-editor .note-statusbar{background-color:#f5f5f5}.note-editor .note-statusbar .note-resizebar{width:100%;height:8px;cursor:s-resize;border-top:1px solid #a9a9a9}.note-editor .note-statusbar .note-resizebar .note-icon-bar{width:20px;margin:1px auto;border-top:1px solid #a9a9a9}.note-editor .note-popover .popover{max-width:none}.note-editor .note-popover .popover .popover-content{padding:5px}.note-editor .note-popover .popover .popover-content a{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}.note-editor .note-popover .popover .popover-content .btn-group+.btn-group{margin-left:5px}.note-editor .note-popover .popover .arrow{left:20px}.note-editor .note-handle .note-control-selection{position:absolute;display:none;border:1px solid black}.note-editor .note-handle .note-control-selection>div{position:absolute}.note-editor .note-handle .note-control-selection .note-control-selection-bg{width:100%;height:100%;background-color:black;-webkit-opacity:.3;-khtml-opacity:.3;-moz-opacity:.3;opacity:.3;-ms-filter:alpha(opacity=30);filter:alpha(opacity=30)}.note-editor .note-handle .note-control-selection .note-control-handle{width:7px;height:7px;border:1px solid black}.note-editor .note-handle .note-control-selection .note-control-holder{width:7px;height:7px;border:1px solid black}.note-editor .note-handle .note-control-selection .note-control-sizing{width:7px;height:7px;background-color:white;border:1px solid black}.note-editor .note-handle .note-control-selection .note-control-nw{top:-5px;left:-5px;border-right:0;border-bottom:0}.note-editor .note-handle .note-control-selection .note-control-ne{top:-5px;right:-5px;border-bottom:0;border-left:none}.note-editor .note-handle .note-control-selection .note-control-sw{bottom:-5px;left:-5px;border-top:0;border-right:0}.note-editor .note-handle .note-control-selection .note-control-se{right:-5px;bottom:-5px;cursor:se-resize}.note-editor .note-handle .note-control-selection .note-control-selection-info{right:0;bottom:0;padding:5px;margin:5px;font-size:12px;color:white;background-color:black;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-opacity:.7;-khtml-opacity:.7;-moz-opacity:.7;opacity:.7;-ms-filter:alpha(opacity=70);filter:alpha(opacity=70)}.note-editor .note-dialog>div{display:none}.note-editor .note-dialog .note-image-dialog .note-dropzone{min-height:100px;margin-bottom:10px;font-size:30px;line-height:4;color:lightgray;text-align:center;border:4px dashed lightgray}.note-editor .note-dialog .note-help-dialog{font-size:12px;color:#ccc;background:transparent;background-color:#222!important;border:0;-webkit-opacity:.9;-khtml-opacity:.9;-moz-opacity:.9;opacity:.9;-ms-filter:alpha(opacity=90);filter:alpha(opacity=90)}.note-editor .note-dialog .note-help-dialog .modal-content{background:transparent;border:1px solid white;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.note-editor .note-dialog .note-help-dialog a{font-size:12px;color:white}.note-editor .note-dialog .note-help-dialog .title{padding-bottom:5px;font-size:14px;font-weight:bold;color:white;border-bottom:white 1px solid}.note-editor .note-dialog .note-help-dialog .modal-close{font-size:14px;color:#dd0;cursor:pointer}.note-editor .note-dialog .note-help-dialog .note-shortcut-layout{width:100%}.note-editor .note-dialog .note-help-dialog .note-shortcut-layout td{vertical-align:top}.note-editor .note-dialog .note-help-dialog .note-shortcut{margin-top:8px}.note-editor .note-dialog .note-help-dialog .note-shortcut th{font-size:13px;color:#dd0;text-align:left}.note-editor .note-dialog .note-help-dialog .note-shortcut td:first-child{min-width:110px;padding-right:10px;font-family:"Courier New";color:#dd0;text-align:right}.note-editor .note-editable{padding:10px;overflow:scroll;outline:0}.note-editor .note-codable{display:none;width:100%;padding:10px;margin-bottom:0;font-family:Menlo,Monaco,monospace,sans-serif;font-size:14px;color:#ccc;background-color:#222;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;box-shadow:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;resize:none}.note-editor .dropdown-menu{min-width:90px}.note-editor .dropdown-menu.right{right:0;left:auto}.note-editor .dropdown-menu.right::before{right:9px;left:auto!important}.note-editor .dropdown-menu.right::after{right:10px;left:auto!important}.note-editor .dropdown-menu li a i{color:deepskyblue;visibility:hidden}.note-editor .dropdown-menu li a.checked i{visibility:visible}.note-editor .note-fontsize-10{font-size:10px}.note-editor .note-color-palette{line-height:1}.note-editor .note-color-palette div .note-color-btn{width:17px;height:17px;padding:0;margin:0;border:1px solid #fff}.note-editor .note-color-palette div .note-color-btn:hover{border:1px solid #000}
--------------------------------------------------------------------------------
/src/main/java/net/filippov/newsportal/domain/User.java:
--------------------------------------------------------------------------------
1 | package net.filippov.newsportal.domain;
2 |
3 | import java.util.Date;
4 | import java.util.Set;
5 |
6 | import javax.persistence.CascadeType;
7 | import javax.persistence.Column;
8 | import javax.persistence.Entity;
9 | import javax.persistence.FetchType;
10 | import javax.persistence.JoinColumn;
11 | import javax.persistence.JoinTable;
12 | import javax.persistence.ManyToMany;
13 | import javax.persistence.NamedQueries;
14 | import javax.persistence.NamedQuery;
15 | import javax.persistence.OneToMany;
16 | import javax.persistence.Table;
17 | import javax.persistence.Temporal;
18 | import javax.persistence.TemporalType;
19 | import javax.validation.constraints.Size;
20 |
21 | import org.hibernate.annotations.Cache;
22 | import org.hibernate.annotations.CacheConcurrencyStrategy;
23 | import org.hibernate.annotations.Formula;
24 | import org.hibernate.validator.constraints.Email;
25 | import org.hibernate.validator.constraints.NotBlank;
26 |
27 | /**
28 | * Stores information about user
29 | *
30 | * @author Oleg Filippov
31 | */
32 | @Entity
33 | @Table(name = "user")
34 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
35 | @NamedQueries({
36 | @NamedQuery(
37 | name = "User.GET_BY_LOGIN",
38 | query = "FROM User u WHERE u.login = :login"),
39 | @NamedQuery(
40 | name = "User.GET_BY_EMAIL",
41 | query = "FROM User u WHERE u.email = :email")
42 | })
43 | public class User extends BaseEntity {
44 |
45 | private static final long serialVersionUID = 8091488929047153516L;
46 |
47 | /**
48 | * User login
49 | */
50 | @Size(min = 4, max = 30, message = "{validation.user.loginSize}")
51 | @Column(name = "login", nullable = false, unique = true)
52 | private String login;
53 |
54 | /**
55 | * User password
56 | */
57 | @Size(min = 7, max = 60, message = "{validation.user.passwordSize}")
58 | @Column(name = "password", nullable = false)
59 | private String password;
60 |
61 | /**
62 | * User fullname
63 | */
64 | @Size(min = 1, max = 50, message = "{validation.user.nameNotBlank}")
65 | @Column(name = "name", nullable = false)
66 | private String name;
67 |
68 | /**
69 | * User e-mail
70 | */
71 | @NotBlank(message = "{validation.user.emailNotBlank}")
72 | @Email(message = "{validation.user.emailValid}")
73 | @Column(name = "email", nullable = false, unique = true, length = 50)
74 | private String email;
75 |
76 | /**
77 | * Registration date and time
78 | */
79 | @Column(name = "registered", insertable = false, updatable = false,
80 | columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
81 | @Temporal(TemporalType.TIMESTAMP)
82 | private final Date registered;
83 |
84 | /**
85 | * State of user (locked or not)
86 | */
87 | @Column(name = "locked", insertable = false,
88 | columnDefinition = "BOOLEAN DEFAULT FALSE")
89 | private boolean locked;
90 |
91 | /**
92 | * State of user (enabled or not)
93 | */
94 | @Column(name = "enabled", insertable = false,
95 | columnDefinition = "BOOLEAN DEFAULT TRUE")
96 | private boolean enabled;
97 |
98 | /**
99 | * Article count of this user
100 | */
101 | @Formula("SELECT COUNT(a.id) FROM Article a WHERE a.user_id = id")
102 | private int articleCount;
103 |
104 | /**
105 | * Comment count of this user
106 | */
107 | @Formula("SELECT COUNT(c.id) FROM Comment c WHERE c.user_id = id")
108 | private int commentCount;
109 |
110 | /**
111 | * User roles
112 | */
113 | @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
114 | @JoinTable(name = "user_role",
115 | joinColumns = {@JoinColumn(name = "user_id") },
116 | inverseJoinColumns = {@JoinColumn(name = "role_id") })
117 | private Set roles;
118 |
119 | /**
120 | * User articles
121 | */
122 | @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
123 | private Set articles;
124 |
125 | /**
126 | * User comments
127 | */
128 | @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
129 | private Set comments;
130 |
131 | /**
132 | * Default constructor initializing needed fields
133 | */
134 | public User() {
135 | registered = new Date();
136 | locked = false;
137 | enabled = true;
138 | }
139 |
140 | /**
141 | * @return user login
142 | */
143 | public String getLogin() {
144 | return login;
145 | }
146 |
147 | /**
148 | * @param login user login to set
149 | */
150 | public void setLogin(String login) {
151 | this.login = login;
152 | }
153 |
154 | /**
155 | * @return user password
156 | */
157 | public String getPassword() {
158 | return password;
159 | }
160 |
161 | /**
162 | * @param password user password to set
163 | */
164 | public void setPassword(String password) {
165 | this.password = password;
166 | }
167 |
168 | /**
169 | * @return user full name
170 | */
171 | public String getName() {
172 | return name;
173 | }
174 |
175 | /**
176 | * @param name user full name to set
177 | */
178 | public void setName(String name) {
179 | this.name = name;
180 | }
181 |
182 | /**
183 | * @return user e-mail
184 | */
185 | public String getEmail() {
186 | return email;
187 | }
188 |
189 | /**
190 | * @param email user e-mail to set
191 | */
192 | public void setEmail(String email) {
193 | this.email = email;
194 | }
195 |
196 | /**
197 | * @return registration date and time
198 | */
199 | public Date getRegistered() {
200 | return registered;
201 | }
202 |
203 | /**
204 | * @return true if user is locked
205 | */
206 | public boolean isLocked() {
207 | return locked;
208 | }
209 |
210 | /**
211 | * @param locked
212 | */
213 | public void setLocked(boolean locked) {
214 | this.locked = locked;
215 | }
216 |
217 | /**
218 | * @return true if user is enabled
219 | */
220 | public boolean isEnabled() {
221 | return enabled;
222 | }
223 |
224 | /**
225 | * @param enabled
226 | */
227 | public void setEnabled(boolean enabled) {
228 | this.enabled = enabled;
229 | }
230 |
231 | /**
232 | * @return this user article count
233 | */
234 | public int getArticleCount() {
235 | return articleCount;
236 | }
237 |
238 | /**
239 | * @return this user comment count
240 | */
241 | public int getCommentCount() {
242 | return commentCount;
243 | }
244 |
245 | /**
246 | * @return user roles
247 | */
248 | public Set getRoles() {
249 | return roles;
250 | }
251 |
252 | /**
253 | * @param roles user roles to set
254 | */
255 | public void setRoles(Set roles) {
256 | this.roles = roles;
257 | }
258 |
259 | /**
260 | * @return this user comments
261 | */
262 | public Set getComments() {
263 | return comments;
264 | }
265 |
266 | /**
267 | * @param comments this user comments to set
268 | */
269 | public void setComments(Set comments) {
270 | this.comments = comments;
271 | }
272 |
273 | /**
274 | * @return this user articles
275 | */
276 | public Set getArticles() {
277 | return articles;
278 | }
279 |
280 | /**
281 | * @param articles this user articles to set
282 | */
283 | public void setArticles(Set articles) {
284 | this.articles = articles;
285 | }
286 |
287 | /**
288 | * @see java.lang.Object#hashCode()
289 | */
290 | @Override
291 | public int hashCode() {
292 | final int prime = 31;
293 | int result = 17;
294 | result = prime * result
295 | + ((getLogin() == null) ? 0 : getLogin().hashCode());
296 | return result;
297 | }
298 |
299 | /**
300 | * @see java.lang.Object#equals(java.lang.Object)
301 | */
302 | @Override
303 | public boolean equals(Object obj) {
304 | if (this == obj)
305 | return true;
306 | if (obj == null)
307 | return false;
308 | if (getClass() != obj.getClass())
309 | return false;
310 |
311 | User other = (User) obj;
312 |
313 | if (getLogin() != null
314 | ? !getLogin().equals(other.getLogin())
315 | : other.getLogin() != null) {
316 | return false;
317 | }
318 | return true;
319 | }
320 |
321 | /**
322 | * @see java.lang.Object#toString()
323 | */
324 | @Override
325 | public String toString() {
326 | return String.format("User[id=%d, login=%s]", getId(), getLogin());
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/src/main/java/net/filippov/newsportal/web/SearchController.java:
--------------------------------------------------------------------------------
1 | package net.filippov.newsportal.web;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import net.filippov.newsportal.domain.Article;
7 | import net.filippov.newsportal.exception.NotFoundException;
8 | import net.filippov.newsportal.service.ArticleService;
9 | import net.filippov.newsportal.web.constants.URL;
10 | import net.filippov.newsportal.web.constants.View;
11 | import net.filippov.newsportal.web.constants.Common;
12 |
13 | import org.springframework.beans.factory.annotation.Autowired;
14 | import org.springframework.context.ApplicationContext;
15 | import org.springframework.context.i18n.LocaleContextHolder;
16 | import org.springframework.stereotype.Controller;
17 | import org.springframework.web.bind.annotation.PathVariable;
18 | import org.springframework.web.bind.annotation.RequestMapping;
19 | import org.springframework.web.bind.annotation.RequestMethod;
20 | import org.springframework.web.bind.annotation.RequestParam;
21 | import org.springframework.web.servlet.ModelAndView;
22 |
23 | /**
24 | * Controller for search-actions
25 | *
26 | * @author Oleg Filippov
27 | */
28 | @Controller
29 | public class SearchController {
30 |
31 | private ArticleService articleService;
32 | private ApplicationContext context;
33 |
34 | /**
35 | * Constructor autowiring needed services
36 | *
37 | * @param articleService {@link ArticleService}
38 | * @param context {@link ApplicationContext}
39 | */
40 | @Autowired
41 | public SearchController(ArticleService articleService,
42 | ApplicationContext context) {
43 | this.articleService = articleService;
44 | this.context = context;
45 | }
46 |
47 | /**
48 | * Search by category - first page
49 | */
50 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_CATEGORY)
51 | public ModelAndView viewArticlesByCategory(@PathVariable("name") String categoryName) {
52 |
53 | return categoryModelAndView(1, categoryName);
54 | }
55 |
56 | /**
57 | * Search by category - custom page
58 | */
59 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_CATEGORY_CUSTOM_PAGE)
60 | public ModelAndView viewArticlesByCategory(@PathVariable("name") String categoryName,
61 | @PathVariable("number") Integer pageNumber) {
62 |
63 | validatePageNumber(pageNumber);
64 | if (pageNumber == 1) {
65 | return new ModelAndView("redirect:" + URL.SEARCH_BY_CATEGORY);
66 | }
67 | return categoryModelAndView(pageNumber, categoryName);
68 | }
69 |
70 | /**
71 | * Prepare searchByCategory-objects for search-ModelAndView
72 | */
73 | private ModelAndView categoryModelAndView(Integer pageNumber, String categoryName) {
74 |
75 | Map articlesData = articleService.getByPageByCategoryName(
76 | pageNumber, Common.ARTICLES_PER_PAGE, categoryName);
77 | String requestUrl = String.format("/category/%s/", categoryName);
78 | String message = String.format(context.getMessage("search.result.byCategory", null,
79 | LocaleContextHolder.getLocale()), categoryName);
80 |
81 | return searchModelAndView(articlesData, pageNumber, message, requestUrl);
82 | }
83 |
84 | /**
85 | * Search by tag - first page
86 | */
87 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_TAG)
88 | public ModelAndView viewArticlesByTag(@PathVariable("name") String tagName) {
89 |
90 | return tagModelAndView(1, tagName);
91 | }
92 |
93 | /**
94 | * Search by tag - custom page
95 | */
96 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_TAG_CUSTOM_PAGE)
97 | public ModelAndView viewArticlesByTag(@PathVariable("name") String tagName,
98 | @PathVariable("number") Integer pageNumber) {
99 |
100 | validatePageNumber(pageNumber);
101 | if (pageNumber == 1) {
102 | return new ModelAndView("redirect:" + URL.SEARCH_BY_TAG);
103 | }
104 | return tagModelAndView(pageNumber, tagName);
105 | }
106 |
107 | /**
108 | * Prepare searchByTag-objects for search-ModelAndView
109 | */
110 | private ModelAndView tagModelAndView(Integer pageNumber, String tagName) {
111 |
112 | Map articlesData = articleService.getByPageByTagName(
113 | pageNumber, Common.ARTICLES_PER_PAGE, tagName);
114 | String requestUrl = String.format("/tags/%s/", tagName);
115 | String message = String.format(context.getMessage("search.result.byTag", null,
116 | LocaleContextHolder.getLocale()), tagName);
117 |
118 | return searchModelAndView(articlesData, pageNumber, message, requestUrl);
119 | }
120 |
121 | /**
122 | * Search by user - first page
123 | */
124 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_USER)
125 | public ModelAndView viewArticlesByUser(@PathVariable("id") Long userId) {
126 |
127 | return userModelAndView(1, userId);
128 | }
129 |
130 | /**
131 | * Search by user - first page
132 | */
133 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_USER_CUSTOM_PAGE)
134 | public ModelAndView viewArticlesByUser(@PathVariable("id") Long userId,
135 | @PathVariable("number") Integer pageNumber) {
136 |
137 | validatePageNumber(pageNumber);
138 | if (pageNumber == 1) {
139 | return new ModelAndView("redirect:" + URL.SEARCH_BY_USER);
140 | }
141 | return userModelAndView(pageNumber, userId);
142 | }
143 |
144 | /**
145 | * Prepare searchByUser-objects for search-ModelAndView
146 | */
147 | private ModelAndView userModelAndView(Integer pageNumber, Long userId) {
148 |
149 | Map articlesData = articleService.getByPageByUserId(
150 | pageNumber, Common.ARTICLES_PER_PAGE, userId);
151 | String userLogin = (String) articlesData.get("userLogin");
152 | String requestUrl = String.format("/user/%d/articles/", userId);
153 | String message = String.format(context.getMessage("search.result.byUser", null,
154 | LocaleContextHolder.getLocale()), userLogin);
155 |
156 | return searchModelAndView(articlesData, pageNumber, message, requestUrl);
157 | }
158 |
159 | /**
160 | * Search by fragment submit-form
161 | */
162 | @RequestMapping(method = RequestMethod.POST, value = URL.SEARCH_BY_FRAGMENT_SUBMIT)
163 | public String searchSubmit(@RequestParam("fragment") String fragment) {
164 |
165 | return "redirect:/search/" + fragment;
166 | }
167 |
168 | /**
169 | * Search by fragment - first page
170 | */
171 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_FRAGMENT)
172 | public ModelAndView viewArticlesByFragment(@PathVariable("fragment") String fragment) {
173 |
174 | return fragmentModelAndView(1, fragment);
175 | }
176 |
177 | /**
178 | * Search by fragment - custom page
179 | */
180 | @RequestMapping(method = RequestMethod.GET, value = URL.SEARCH_BY_FRAGMENT_CUSTOM_PAGE)
181 | public ModelAndView viewArticlesByFragment(@PathVariable("fragment") String fragment,
182 | @PathVariable("number") Integer pageNumber) {
183 |
184 | validatePageNumber(pageNumber);
185 | if (pageNumber == 1) {
186 | return new ModelAndView("redirect:" + URL.SEARCH_BY_FRAGMENT);
187 | }
188 | return fragmentModelAndView(pageNumber, fragment);
189 | }
190 |
191 | /**
192 | * Prepare searchByFragment-objects for search-ModelAndView
193 | */
194 | private ModelAndView fragmentModelAndView(Integer pageNumber, String fragment) {
195 |
196 | Map articlesData = articleService.getByPageByFragment(
197 | pageNumber, Common.ARTICLES_PER_PAGE, fragment);
198 | String requestUrl = String.format("/search/%s/", fragment);
199 | String message = String.format(context.getMessage("search.result.byFragment", null,
200 | LocaleContextHolder.getLocale()), fragment);
201 |
202 | return searchModelAndView(articlesData, pageNumber, message, requestUrl);
203 | }
204 |
205 | /**
206 | * Fill search-ModelAndView with needed objects
207 | *
208 | * @param articlesData Map with articles
209 | * @param pageNumber number of page
210 | * @param message localized search-message
211 | * @param requestUrl root URL
212 | * @return filled {@link ModelAndView}
213 | */
214 | private ModelAndView searchModelAndView(Map articlesData,
215 | Integer pageNumber, String message, String requestUrl) {
216 |
217 | // Set required model attributes
218 | ModelAndView mav = new ModelAndView(View.SEARCH)
219 | .addObject("message", message)
220 | .addObject("requestUrl", requestUrl)
221 | .addObject("currentPage", pageNumber);
222 |
223 | if (articlesData.isEmpty()) { // articleCount == 0
224 | return mav;
225 | }
226 |
227 | Integer pageCount = (Integer) articlesData.get("pageCount");
228 |
229 | @SuppressWarnings("unchecked")
230 | List articlesByPage = (List) articlesData.get("articlesByPage");
231 |
232 | return mav.addObject("pageCount", pageCount)
233 | .addObject("articlesByPage", articlesByPage);
234 | }
235 |
236 | private void validatePageNumber(int pageNumber) {
237 | if (pageNumber < 1) {
238 | throw new NotFoundException("Page < 1");
239 | }
240 | }
241 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | net.filippov.newsportal
6 | newsportal
7 | Newsportal
8 | war
9 | 1.0.2
10 |
11 |
12 | UTF-8
13 | 1.7
14 | 3.2.4.RELEASE
15 | 3.1.4.RELEASE
16 | 3.0.1
17 | 4.2.7.Final
18 | 4.1.0.Final
19 | 2.6.8
20 | 1.7.0
21 | 1.4
22 | 1.3.174
23 | 1.7.2
24 | 1.2.17
25 |
26 |
27 |
28 |
29 |
30 | org.springframework
31 | spring-core
32 | ${spring.version}
33 |
34 |
35 |
36 | commons-logging
37 | commons-logging
38 |
39 |
40 |
41 |
42 | org.springframework
43 | spring-context
44 | ${spring.version}
45 |
46 |
47 | org.springframework
48 | spring-webmvc
49 | ${spring.version}
50 |
51 |
52 | org.springframework.security
53 | spring-security-core
54 | ${spring.security.version}
55 |
56 |
57 | org.springframework.security
58 | spring-security-web
59 | ${spring.security.version}
60 |
61 |
62 | org.springframework.security
63 | spring-security-config
64 | ${spring.security.version}
65 |
66 |
67 | org.springframework.security
68 | spring-security-taglibs
69 | ${spring.security.version}
70 |
71 |
72 |
73 |
74 | org.springframework
75 | spring-orm
76 | ${spring.version}
77 |
78 |
79 | org.hibernate
80 | hibernate-core
81 | ${hibernate.version}
82 |
83 |
84 | org.hibernate
85 | hibernate-entitymanager
86 | ${hibernate.version}
87 |
88 |
89 | org.hibernate
90 | hibernate-ehcache
91 | ${hibernate.version}
92 |
93 |
94 | commons-dbcp
95 | commons-dbcp
96 | ${dbcp.version}
97 |
98 |
99 | com.h2database
100 | h2
101 | ${h2database.version}
102 |
103 |
104 |
105 |
106 | javax.servlet
107 | javax.servlet-api
108 | 3.0.1
109 | provided
110 |
111 |
112 | javax.servlet.jsp
113 | jsp-api
114 | 2.1
115 | provided
116 |
117 |
118 | javax.servlet
119 | jstl
120 | 1.2
121 |
122 |
123 |
124 |
125 | javax.inject
126 | javax.inject
127 | 1
128 |
129 |
130 |
131 |
132 | org.hibernate
133 | hibernate-validator
134 | ${hibernate.validator.version}
135 |
136 |
137 | net.sf.ehcache
138 | ehcache-core
139 | ${ehcache.version}
140 |
141 |
142 |
143 |
144 | org.aspectj
145 | aspectjrt
146 | ${aspectj.version}
147 |
148 |
149 |
150 |
151 | org.slf4j
152 | slf4j-api
153 | ${slf4j.version}
154 |
155 |
156 | org.slf4j
157 | jcl-over-slf4j
158 | ${slf4j.version}
159 | runtime
160 |
161 |
162 | org.slf4j
163 | slf4j-log4j12
164 | ${slf4j.version}
165 | runtime
166 |
167 |
168 | log4j
169 | apache-log4j-extras
170 | ${log4j.version}
171 |
172 |
173 | javax.mail
174 | mail
175 |
176 |
177 | javax.jms
178 | jms
179 |
180 |
181 | com.sun.jdmk
182 | jmxtools
183 |
184 |
185 | com.sun.jmx
186 | jmxri
187 |
188 |
189 | runtime
190 |
191 |
192 |
193 |
194 | junit
195 | junit
196 | 4.7
197 | test
198 |
199 |
200 | org.springframework
201 | spring-test
202 | ${spring.version}
203 | test
204 |
205 |
206 |
207 |
208 | commons-io
209 | commons-io
210 | 1.3.2
211 |
212 |
213 | commons-fileupload
214 | commons-fileupload
215 | 1.2
216 |
217 |
218 |
219 |
220 |
221 |
222 | maven-eclipse-plugin
223 | 2.9
224 |
225 |
226 | org.springframework.ide.eclipse.core.springnature
227 |
228 |
229 | org.springframework.ide.eclipse.core.springbuilder
230 |
231 | true
232 | true
233 |
234 |
235 |
236 | org.apache.maven.plugins
237 | maven-compiler-plugin
238 | 2.5.1
239 |
240 | 1.7
241 | 1.7
242 | -Xlint:all
243 | true
244 | true
245 | true
246 |
247 |
248 |
249 | org.apache.tomcat.maven
250 | tomcat7-maven-plugin
251 | 2.2
252 |
253 | /newsportal
254 |
255 |
256 |
257 | org.codehaus.mojo
258 | exec-maven-plugin
259 | 1.2.1
260 |
261 | org.test.int1.Main
262 |
263 |
264 |
265 | maven-war-plugin
266 | 2.1.1
267 |
268 | WEB-INF/web.xml
269 |
270 |
271 |
272 |
273 |
274 |
--------------------------------------------------------------------------------
/src/main/java/net/filippov/newsportal/domain/Article.java:
--------------------------------------------------------------------------------
1 | package net.filippov.newsportal.domain;
2 |
3 | import java.util.Date;
4 | import java.util.Set;
5 |
6 | import javax.persistence.CascadeType;
7 | import javax.persistence.Column;
8 | import javax.persistence.Entity;
9 | import javax.persistence.FetchType;
10 | import javax.persistence.JoinColumn;
11 | import javax.persistence.JoinTable;
12 | import javax.persistence.ManyToMany;
13 | import javax.persistence.ManyToOne;
14 | import javax.persistence.NamedQueries;
15 | import javax.persistence.NamedQuery;
16 | import javax.persistence.OneToMany;
17 | import javax.persistence.OrderBy;
18 | import javax.persistence.Table;
19 | import javax.persistence.Temporal;
20 | import javax.persistence.TemporalType;
21 |
22 | import org.hibernate.annotations.Cache;
23 | import org.hibernate.annotations.CacheConcurrencyStrategy;
24 | import org.hibernate.annotations.Formula;
25 | import org.hibernate.validator.constraints.NotBlank;
26 |
27 | /**
28 | * Represents an article with String content
29 | *
30 | * @author Oleg Filippov
31 | */
32 | @Entity
33 | @Table(name = "article")
34 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
35 | @NamedQueries({
36 | @NamedQuery(
37 | name = "Article.GET_ALL",
38 | query = "FROM Article a ORDER BY a.created DESC"),
39 | @NamedQuery(
40 | name = "Article.GET_ALL_BY_USER_ID",
41 | query = "FROM Article a WHERE a.author.id = :id ORDER BY a.created DESC"),
42 | @NamedQuery(
43 | name = "Article.GET_ALL_BY_CATEGORY_NAME",
44 | query = "FROM Article a WHERE a.category.name = :name ORDER BY a.created DESC"),
45 | @NamedQuery(
46 | name = "Article.GET_ALL_BY_TAG_NAME",
47 | query = "SELECT a FROM Tag t JOIN t.articles a WHERE t.name = :name ORDER BY a.created DESC"),
48 | @NamedQuery(
49 | name = "Article.GET_ALL_BY_FRAGMENT",
50 | query = "FROM Article a WHERE a.content LIKE :fragment ORDER BY a.created DESC"),
51 | @NamedQuery(
52 | name = "Article.GET_COUNT_BY_FRAGMENT",
53 | query = "SELECT COUNT(a.id) FROM Article a WHERE a.content LIKE :fragment")
54 | })
55 | public class Article extends BaseEntity {
56 |
57 | private static final long serialVersionUID = 38150497082508411L;
58 |
59 | /**
60 | * Article title
61 | */
62 | @NotBlank(message = "{validation.article.title}")
63 | @Column(name = "title", nullable = false, length = 100)
64 | private String title;
65 |
66 | /**
67 | * Article preview
68 | */
69 | @NotBlank(message = "{validation.article.preview}")
70 | @Column(name = "preview", nullable = false)
71 | private String preview;
72 |
73 | /**
74 | * Article content
75 | */
76 | @NotBlank(message = "{validation.article.content}")
77 | @Column(name = "content", nullable = false, length = 65535)
78 | private String content;
79 |
80 | /**
81 | * Article creation date and time
82 | */
83 | @Column(name = "created", insertable = false, updatable = false,
84 | columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
85 | @Temporal(TemporalType.TIMESTAMP)
86 | private final Date created;
87 |
88 | /**
89 | * Article modification date and time
90 | */
91 | @Column(name = "last_modified")
92 | @Temporal(TemporalType.TIMESTAMP)
93 | private Date lastModified;
94 |
95 | /**
96 | * Article view count
97 | */
98 | @Column(name = "view_count", insertable = false, columnDefinition = "INT DEFAULT 0")
99 | private int viewCount;
100 |
101 | /**
102 | * Article comment count
103 | */
104 | @Formula("SELECT COUNT(id) FROM Comment c WHERE c.article_id = id")
105 | private int commentCount;
106 |
107 | /**
108 | * Author of this article
109 | */
110 | @ManyToOne(fetch = FetchType.EAGER)
111 | @JoinColumn(name = "user_id", nullable = false, updatable = false)
112 | private User author;
113 |
114 | /**
115 | * Category of this article
116 | */
117 | @ManyToOne
118 | @JoinColumn(name = "category_id")
119 | private Category category;
120 |
121 | /**
122 | * Comments to this article
123 | */
124 | @OneToMany(mappedBy = "article", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
125 | @OrderBy("created desc")
126 | private Set comments;
127 |
128 | /**
129 | * Tags of this article
130 | */
131 | @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
132 | @JoinTable(name = "article_tag",
133 | joinColumns = {@JoinColumn(name = "article_id") },
134 | inverseJoinColumns = {@JoinColumn(name = "tag_id") })
135 | @OrderBy("name")
136 | private Set tags;
137 |
138 | /**
139 | * Default constructor initializing needed fields
140 | */
141 | public Article() {
142 | created = new Date();
143 | viewCount = 0;
144 | }
145 |
146 | /**
147 | * @return article title
148 | */
149 | public String getTitle() {
150 | return title;
151 | }
152 |
153 | /**
154 | * @param title article title to set
155 | */
156 | public void setTitle(String title) {
157 | this.title = title;
158 | }
159 |
160 | /**
161 | * @return article preview
162 | */
163 | public String getPreview() {
164 | return preview;
165 | }
166 |
167 | /**
168 | * @param preview article preview to set
169 | */
170 | public void setPreview(String preview) {
171 | this.preview = preview;
172 | }
173 |
174 | /**
175 | * @return article content
176 | */
177 | public String getContent() {
178 | return content;
179 | }
180 |
181 | /**
182 | * @param content article content to set
183 | */
184 | public void setContent(String content) {
185 | this.content = content;
186 | }
187 |
188 | /**
189 | * @return article date and time of creation
190 | */
191 | public Date getCreated() {
192 | return created;
193 | }
194 |
195 | /**
196 | * @return article modification date and time
197 | */
198 | public Date getLastModified() {
199 | return lastModified;
200 | }
201 |
202 | /**
203 | * @param lastModified date and time when article was last modified
204 | */
205 | public void setLastModified(Date lastModified) {
206 | this.lastModified = lastModified;
207 | }
208 |
209 | /**
210 | * @return article view count
211 | */
212 | public int getViewCount() {
213 | return viewCount;
214 | }
215 |
216 | /**
217 | * @param viewCount article view count to set
218 | */
219 | public void setViewCount(int viewCount) {
220 | this.viewCount = viewCount;
221 | }
222 |
223 | /**
224 | * @return article comment count
225 | */
226 | public int getCommentCount() {
227 | return commentCount;
228 | }
229 |
230 | /**
231 | * @return author of this article
232 | */
233 | public User getAuthor() {
234 | return author;
235 | }
236 |
237 | /**
238 | * @param author author of this article to set
239 | */
240 | public void setAuthor(User author) {
241 | this.author = author;
242 | }
243 |
244 | /**
245 | * @return category of this article
246 | */
247 | public Category getCategory() {
248 | return category;
249 | }
250 |
251 | /**
252 | * @param category category of this article to set
253 | */
254 | public void setCategory(Category category) {
255 | this.category = category;
256 | }
257 |
258 | /**
259 | * @return comments to this article
260 | */
261 | public Set getComments() {
262 | return comments;
263 | }
264 |
265 | /**
266 | * @param comments comments to this article to set
267 | */
268 | public void setComments(Set comments) {
269 | this.comments = comments;
270 | }
271 |
272 | /**
273 | * @return tags of this article
274 | */
275 | public Set getTags() {
276 | return tags;
277 | }
278 |
279 | /**
280 | * @param tags tags of this article to set
281 | */
282 | public void setTags(Set tags) {
283 | this.tags = tags;
284 | }
285 |
286 | /**
287 | * @see java.lang.Object#hashCode()
288 | */
289 | @Override
290 | public int hashCode() {
291 | final int prime = 31;
292 | int result = 17;
293 | result = prime * result
294 | + ((getTitle() == null) ? 0 : getTitle().hashCode());
295 | result = prime * result
296 | + ((getCreated() == null) ? 0 : getCreated().hashCode());
297 | return result;
298 | }
299 |
300 | /**
301 | * @see java.lang.Object#equals(java.lang.Object)
302 | */
303 | @Override
304 | public boolean equals(Object obj) {
305 | if (this == obj)
306 | return true;
307 | if (obj == null)
308 | return false;
309 | if (getClass() != obj.getClass())
310 | return false;
311 |
312 | Article other = (Article) obj;
313 |
314 | if (getTitle() != null
315 | ? !getTitle().equals(other.getTitle())
316 | : other.getTitle() != null) {
317 | return false;
318 | }
319 | if (getCreated() != null
320 | ? getCreated().compareTo(other.getCreated()) != 0
321 | : other.getCreated() != null) {
322 | return false;
323 | }
324 | return true;
325 | }
326 |
327 | /**
328 | * @see java.lang.Object#toString()
329 | */
330 | @Override
331 | public String toString() {
332 | return String.format("Article[id=%d, author=%s]",
333 | getId(), getAuthor() == null ? "null" : getAuthor().getLogin());
334 | }
335 | }
336 |
--------------------------------------------------------------------------------
135 |