View Javadoc
1   package net.avcompris.examples.users3.core.impl;
2   
3   import static com.google.common.base.Preconditions.checkNotNull;
4   import static net.avcompris.commons3.core.DateTimeHolderImpl.toDateTimeHolder;
5   import static net.avcompris.commons3.core.DateTimeHolderImpl.toDateTimeHolderOrNull;
6   import static net.avcompris.commons3.databeans.DataBeans.instantiate;
7   import static net.avcompris.examples.shared3.core.impl.Validations.assertNonSuperadminUsername;
8   import static net.avcompris.examples.shared3.core.impl.Validations.assertValidPassword;
9   import static net.avcompris.examples.shared3.core.impl.Validations.assertValidUsername;
10  import static org.apache.commons.lang3.StringUtils.isBlank;
11  
12  import java.io.IOException;
13  import java.sql.SQLException;
14  
15  import javax.annotation.Nullable;
16  
17  import org.apache.commons.lang3.NotImplementedException;
18  import org.apache.commons.logging.Log;
19  import org.springframework.beans.factory.annotation.Autowired;
20  import org.springframework.stereotype.Service;
21  
22  import net.avcompris.commons.query.FilterSyntaxException;
23  import net.avcompris.commons.query.impl.FilteringsFactory;
24  import net.avcompris.commons3.api.EnumRole;
25  import net.avcompris.commons3.api.User;
26  import net.avcompris.commons3.api.exception.DaoException;
27  import net.avcompris.commons3.api.exception.InvalidQueryFilteringException;
28  import net.avcompris.commons3.api.exception.InvalidRoleException;
29  import net.avcompris.commons3.api.exception.ServiceException;
30  import net.avcompris.commons3.api.exception.UnauthorizedException;
31  import net.avcompris.commons3.api.exception.UsernameAlreadyExistsException;
32  import net.avcompris.commons3.api.exception.UsernameNotFoundException;
33  import net.avcompris.commons3.core.Permissions;
34  import net.avcompris.commons3.core.impl.AbstractServiceImpl;
35  import net.avcompris.commons3.core.impl.SortBysParser;
36  import net.avcompris.commons3.dao.exception.DuplicateEntityException;
37  import net.avcompris.commons3.utils.Clock;
38  import net.avcompris.commons3.utils.LogFactory;
39  import net.avcompris.examples.shared3.Role;
40  import net.avcompris.examples.users3.api.UserCreate;
41  import net.avcompris.examples.users3.api.UserInfo;
42  import net.avcompris.examples.users3.api.UserUpdate;
43  import net.avcompris.examples.users3.api.UsersInfo;
44  import net.avcompris.examples.users3.api.UsersQuery;
45  import net.avcompris.examples.users3.api.UsersQuery.SortBy;
46  import net.avcompris.examples.users3.core.api.MutableUserInfo;
47  import net.avcompris.examples.users3.core.api.MutableUsersInfo;
48  import net.avcompris.examples.users3.core.api.UsersService;
49  import net.avcompris.examples.users3.dao.AuthDao;
50  import net.avcompris.examples.users3.dao.UserDto;
51  import net.avcompris.examples.users3.dao.UsersDao;
52  import net.avcompris.examples.users3.dao.UsersDto;
53  import net.avcompris.examples.users3.dao.UsersDtoQuery;
54  import net.avcompris.examples.users3.query.UserFiltering;
55  import net.avcompris.examples.users3.query.UserFilterings;
56  
57  @Service
58  public final class UsersServiceImpl extends AbstractServiceImpl implements UsersService {
59  
60  	private static final Log logger = LogFactory.getLog(UsersServiceImpl.class);
61  
62  	private static final UsersDtoQuery.SortBy[] DEFAULT_SORT_BYS = new UsersDtoQuery.SortBy[] {
63  			UsersDtoQuery.SortBy.SORT_BY_USERNAME };
64  
65  	private static final UsersDtoQuery.Expand[] DEFAULT_EXPANDS = new UsersDtoQuery.Expand[] {
66  			UsersDtoQuery.Expand.EXPAND_ALL };
67  
68  	private final AuthDao authDao;
69  	private final UsersDao usersDao;
70  
71  	private static final SortBysParser<SortBy> SORT_BYS_PARSER = new SortBysParser<SortBy>(SortBy.class,
72  			SortBy.values());
73  
74  	@Autowired
75  	public UsersServiceImpl(final Permissions permissions, final Clock clock, //
76  			final UsersDao usersDao, //
77  			final AuthDao authDao) {
78  
79  		super(permissions, clock);
80  
81  		this.authDao = checkNotNull(authDao, "authDao");
82  		this.usersDao = checkNotNull(usersDao, "usersDao");
83  	}
84  
85  	private static UsersDtoQuery.SortBy[] sortBys2Dto(final UsersQuery.SortBy... sortBys) {
86  
87  		if (sortBys == null || sortBys.length == 0) {
88  			return DEFAULT_SORT_BYS;
89  		}
90  
91  		final UsersDtoQuery.SortBy[] dto = new UsersDtoQuery.SortBy[sortBys.length];
92  
93  		for (int i = 0; i < sortBys.length; ++i) {
94  			dto[i] = sortBy2Dto(sortBys[i]);
95  		}
96  
97  		return dto;
98  	}
99  
100 	private static UsersDtoQuery.SortBy sortBy2Dto(final UsersQuery.SortBy sortBy) {
101 
102 		checkNotNull(sortBy, "sortBy");
103 
104 		switch (sortBy) {
105 		case SORT_BY_USERNAME:
106 			return UsersDtoQuery.SortBy.SORT_BY_USERNAME;
107 		case SORT_BY_USERNAME_DESC:
108 			return UsersDtoQuery.SortBy.SORT_BY_USERNAME_DESC;
109 		case SORT_BY_ROLE:
110 			return UsersDtoQuery.SortBy.SORT_BY_ROLENAME;
111 		case SORT_BY_ROLE_DESC:
112 			return UsersDtoQuery.SortBy.SORT_BY_ROLENAME_DESC;
113 		case SORT_BY_ENABLED:
114 			return UsersDtoQuery.SortBy.SORT_BY_ENABLED;
115 		case SORT_BY_ENABLED_DESC:
116 			return UsersDtoQuery.SortBy.SORT_BY_ENABLED_DESC;
117 		case SORT_BY_CREATED_AT:
118 			return UsersDtoQuery.SortBy.SORT_BY_CREATED_AT;
119 		case SORT_BY_CREATED_AT_DESC:
120 			return UsersDtoQuery.SortBy.SORT_BY_CREATED_AT_DESC;
121 		case SORT_BY_UPDATED_AT:
122 			return UsersDtoQuery.SortBy.SORT_BY_UPDATED_AT;
123 		case SORT_BY_UPDATED_AT_DESC:
124 			return UsersDtoQuery.SortBy.SORT_BY_UPDATED_AT_DESC;
125 		case SORT_BY_LAST_ACTIVE_AT:
126 			return UsersDtoQuery.SortBy.SORT_BY_LAST_ACTIVE_AT;
127 		case SORT_BY_LAST_ACTIVE_AT_DESC:
128 			return UsersDtoQuery.SortBy.SORT_BY_LAST_ACTIVE_AT_DESC;
129 		case SORT_BY_REVISION:
130 			return UsersDtoQuery.SortBy.SORT_BY_REVISION;
131 		case SORT_BY_REVISION_DESC:
132 			return UsersDtoQuery.SortBy.SORT_BY_REVISION_DESC;
133 		default:
134 			throw new NotImplementedException("sortBy: " + sortBy);
135 		}
136 	}
137 
138 	private static UsersDtoQuery.Expand[] expands2Dto(final UsersQuery.Expand... expands) {
139 
140 		if (expands == null || expands.length == 0) {
141 			return DEFAULT_EXPANDS;
142 		}
143 
144 		throw new NotImplementedException("");
145 	}
146 
147 	@Override
148 	public UsersInfo getUsers(final String correlationId, final User user, @Nullable final UsersQuery query)
149 			throws ServiceException {
150 
151 		checkNotNull(correlationId, "correlationId");
152 		checkNotNull(user, "user");
153 
154 		LogFactory.setCorrelationId(correlationId);
155 
156 		// 0. PERMISSIONS
157 
158 		permissions.assertAuthorized(correlationId, user, "query", query);
159 
160 		// 1. LOGIC
161 
162 		return privateGetUsers(query);
163 	}
164 
165 	private UsersInfo privateGetUsers(@Nullable final UsersQuery query) throws ServiceException {
166 
167 		final long startMs = System.currentTimeMillis();
168 
169 		final int start = getQueryStart(query, 0);
170 		final int limit = getQueryLimit(query, 10);
171 
172 		final UsersDtoQuery.Expand[] expandDtos;
173 
174 		if (query == null) {
175 			expandDtos = DEFAULT_EXPANDS;
176 		} else {
177 			expandDtos = expands2Dto(query.getExpands());
178 		}
179 
180 		final UsersDtoQuery dtoQuery = instantiate(UsersDtoQuery.class) //
181 				.setFiltering(query != null ? query.getFiltering() : null) //
182 				.setSortBys(query != null ? sortBys2Dto(query.getSortBys()) : DEFAULT_SORT_BYS) // ;
183 				.setExpands(expandDtos) //
184 				.setStart(start) //
185 				.setLimit(limit); //
186 
187 		final UsersDto usersDto = wrap(()
188 
189 		-> usersDao.getUsers(dtoQuery));
190 
191 		final MutableUsersInfo result = instantiate(MutableUsersInfo.class) //
192 				.setStart(start) //
193 				.setLimit(limit) //
194 				.setSize(usersDto.getResults().length) //
195 				.setTotal(usersDto.getTotal()) //
196 				.setSqlWhereClause(usersDto.getSqlWhereClause());
197 
198 		for (final UserDto userDto : usersDto.getResults()) {
199 
200 			result.addToResults(dto2UserInfo(userDto));
201 		}
202 
203 		final int tookMs = (int) (System.currentTimeMillis() - startMs);
204 
205 		return result.setTookMs(tookMs);
206 	}
207 
208 	private static UserInfo dto2UserInfo(final UserDto dto) {
209 
210 		checkNotNull(dto, "dto");
211 
212 		return instantiate(MutableUserInfo.class) //
213 				.setUsername(dto.getUsername()) //
214 				.setRole(Role.valueOf(dto.getRolename())) //
215 				.setPreferredLang(dto.getPreferredLang()) //
216 				.setPreferredTimeZone(dto.getPreferredTimeZone()) //
217 				.setCreatedAt(toDateTimeHolder(dto.getCreatedAt())) //
218 				.setUpdatedAt(toDateTimeHolder(dto.getUpdatedAt())) //
219 				.setRevision(dto.getRevision()) //
220 				.setLastActiveAt(toDateTimeHolderOrNull(dto.getLastActiveAt())) //
221 				.setEnabled(dto.isEnabled());
222 	}
223 
224 	@Override
225 	public UserInfo createUser(final String correlationId, final User user, final String username,
226 			final UserCreate create) throws ServiceException {
227 
228 		checkNotNull(user, "user");
229 		checkNotNull(correlationId, "correlationId");
230 		checkNotNull(username, "username");
231 		checkNotNull(create, "create");
232 
233 		LogFactory.setCorrelationId(correlationId);
234 
235 		// 0. PERMISSIONS
236 
237 		permissions.assertAuthorized(correlationId, user, "create", create);
238 
239 		// 1. LOGIC
240 
241 		return privateCreateUser(correlationId, user, username, create);
242 	}
243 
244 	private UserInfo privateCreateUser(final String correlationId, final User user, final String username,
245 			final UserCreate create) throws ServiceException {
246 
247 		final String password = create.getPassword();
248 
249 		assertValidUsername(username);
250 		assertNonSuperadminUsername(username);
251 
252 		if (password != null) {
253 
254 			assertValidPassword(password);
255 		}
256 
257 		final Role role = create.getRole();
258 
259 		if (role == Role.ANONYMOUS || role == Role.SUPERADMIN) {
260 
261 			throw new InvalidRoleException("role: " + role);
262 		}
263 
264 		assertRoleCanManageRole(user.getRole(), role);
265 
266 		try {
267 
268 			usersDao.createUser( //
269 					username, //
270 					role.getRolename(), //
271 					create.getPreferredLang(), //
272 					create.getPreferredTimeZone(), //
273 					create.isEnabled());
274 
275 			if (password == null) {
276 
277 				authDao.removeUserPassword(username); // In case the DB was incoherent
278 
279 			} else {
280 
281 				authDao.setUserPassword(username, password);
282 			}
283 
284 		} catch (final IOException | SQLException e) {
285 
286 			throw new DaoException(e);
287 
288 		} catch (final DuplicateEntityException e) {
289 
290 			throw new UsernameAlreadyExistsException(username, e);
291 		}
292 
293 		return privateGetUser(username);
294 	}
295 
296 	@Override
297 	public UserInfo getUser(final String correlationId, final User user, final String username)
298 			throws ServiceException {
299 
300 		checkNotNull(correlationId, "correlationId");
301 		checkNotNull(user, "user");
302 		checkNotNull(username, "username");
303 
304 		LogFactory.setCorrelationId(correlationId);
305 
306 		// 0. PERMISSIONS
307 
308 		permissions.assertAuthorized(correlationId, user, "username", username);
309 
310 		// 1. LOGIC
311 
312 		return privateGetUser(username);
313 	}
314 
315 	@Override
316 	public boolean hasUser(final String correlationId, final User user, final String username) throws ServiceException {
317 
318 		checkNotNull(correlationId, "correlationId");
319 		checkNotNull(user, "user");
320 		checkNotNull(username, "username");
321 
322 		LogFactory.setCorrelationId(correlationId);
323 
324 		// 0. PERMISSIONS
325 
326 		permissions.assertAuthorized(correlationId, user, "username", username);
327 
328 		// 1. LOGIC
329 
330 		assertValidUsername(username);
331 
332 		final UserDto dto = wrap(()
333 
334 		-> usersDao.getUser(username));
335 
336 		return dto != null;
337 	}
338 
339 	private UserInfo privateGetUser(final String username) throws ServiceException {
340 
341 		checkNotNull(username, "username");
342 
343 		assertValidUsername(username);
344 
345 		final UserDto dto = wrap(()
346 
347 		-> usersDao.getUser(username));
348 
349 		if (dto == null) {
350 
351 			throw new UsernameNotFoundException(username);
352 		}
353 
354 		return dto2UserInfo(dto);
355 	}
356 
357 	@Override
358 	public UserInfo getUserMe(final String correlationId, final User user) throws ServiceException {
359 
360 		checkNotNull(correlationId, "correlationId");
361 		checkNotNull(user, "user");
362 
363 		LogFactory.setCorrelationId(correlationId);
364 
365 		// 0. PERMISSIONS
366 
367 		permissions.assertAuthorized(correlationId, user);
368 
369 		// 1. LOGIC
370 
371 		return privateGetUser(user.getUsername());
372 	}
373 
374 	@Override
375 	@Nullable
376 	public UsersQuery validateUsersQuery(final String correlationId, final User user, @Nullable final String q,
377 			@Nullable final String sort, @Nullable final Integer start, @Nullable final Integer limit,
378 			@Nullable final String expand) throws ServiceException {
379 
380 		checkNotNull(correlationId, "correlationId");
381 		checkNotNull(user, "user");
382 
383 		LogFactory.setCorrelationId(correlationId);
384 
385 		// 0. PERMISSIONS
386 
387 		permissions.assertAuthorized(correlationId, user);
388 
389 		// 1. LOGIC
390 
391 		if (isBlank(q) && isBlank(sort) && start == null && limit == null && isBlank(expand)) {
392 			return null;
393 		}
394 
395 		final UsersQuery query = instantiate(UsersQuery.class);
396 
397 		if (start != null) {
398 
399 			if (start < 0) {
400 				throw new InvalidQueryFilteringException("Query.start should be >= 0, but was: " + start);
401 			}
402 
403 			query.setStart(start);
404 		}
405 
406 		if (limit != null) {
407 
408 			if (limit < 0) {
409 				throw new InvalidQueryFilteringException("Query.limit should be >= 0, but was: " + limit);
410 			}
411 
412 			query.setLimit(limit);
413 		}
414 
415 		if (!isBlank(q)) {
416 
417 			if (q.length() > 1000) {
418 				throw new InvalidQueryFilteringException(
419 						"Query should be at most 1,000 characters, but was: " + q.length());
420 			}
421 
422 			final UserFilterings filterings = FilteringsFactory.instantiate(UserFilterings.class);
423 
424 			final UserFiltering filtering;
425 
426 			try {
427 
428 				filtering = filterings.parse(q);
429 
430 			} catch (final FilterSyntaxException e) {
431 
432 				throw new InvalidQueryFilteringException(e);
433 			}
434 
435 			query.setFiltering(filtering);
436 		}
437 
438 		if (!isBlank(sort)) {
439 
440 			query.setSortBys(SORT_BYS_PARSER.parse(sort));
441 		}
442 
443 		if (!isBlank(expand)) {
444 
445 			throw new NotImplementedException("expand: " + expand);
446 		}
447 
448 		return query;
449 	}
450 
451 	@Override
452 	public UserInfo updateUserMe(final String correlationId, final User user, final UserUpdate update)
453 			throws ServiceException {
454 
455 		checkNotNull(correlationId, "correlationId");
456 		checkNotNull(user, "user");
457 		checkNotNull(update, "update");
458 
459 		LogFactory.setCorrelationId(correlationId);
460 
461 		// 0. PERMISSIONS
462 
463 		permissions.assertAuthorized(correlationId, user);
464 
465 		// 1. LOGIC
466 
467 		final String username = user.getUsername();
468 
469 		if (logger.isInfoEnabled()) {
470 			logger.info("updateUserMe(): username: " + username + ", " + update + "...");
471 		}
472 
473 		@Nullable
474 		final String password = update.getPassword();
475 
476 		final UserDto toDto = wrap(() -> {
477 
478 			final Role role = extractUpdateRole(update, () -> //
479 			getUserMe(correlationId, user).getRole());
480 
481 			final boolean enabled = extractUpdateEnabled(update, () -> //
482 			usersDao.getUser(username).isEnabled());
483 
484 			usersDao.updateUser(username, //
485 					role.getRolename(), //
486 					update.getPreferredLang(), //
487 					update.getPreferredTimeZone(), //
488 					enabled, //
489 					update.getFromRevision());
490 
491 			if (password != null) {
492 
493 				authDao.setUserPassword(username, password);
494 			}
495 
496 			return usersDao.getUser(username);
497 
498 		});
499 
500 		if (logger.isInfoEnabled()) {
501 			logger.info("updateUserMe(): Ending.");
502 		}
503 
504 		return dto2UserInfo(toDto);
505 	}
506 
507 	private Role extractUpdateRole(final UserUpdate update, final Action<Role> action)
508 			throws IOException, SQLException, ServiceException {
509 
510 		final Role role;
511 
512 		if (update.getRole() != null) {
513 
514 			role = update.getRole();
515 
516 		} else {
517 
518 			role = action.execute();
519 		}
520 
521 		if (role == Role.ANONYMOUS || role == Role.SUPERADMIN) {
522 
523 			throw new InvalidRoleException("role: " + role);
524 		}
525 
526 		return role;
527 	}
528 
529 	private boolean extractUpdateEnabled(final UserUpdate update, final Action<Boolean> action)
530 			throws IOException, SQLException, ServiceException {
531 
532 		if (update.isEnabled() != null) {
533 
534 			return update.isEnabled();
535 		}
536 
537 		return action.execute();
538 	}
539 
540 	@Override
541 	public UserInfo updateUser(final String correlationId, final User user, final String username,
542 			final UserUpdate update) throws ServiceException {
543 
544 		checkNotNull(correlationId, "correlationId");
545 		checkNotNull(user, "user");
546 		checkNotNull(username, "username");
547 		checkNotNull(update, "update");
548 
549 		LogFactory.setCorrelationId(correlationId);
550 
551 		// 0. PERMISSIONS
552 
553 		permissions.assertAuthorized(correlationId, user);
554 
555 		// 1. LOGIC
556 
557 		if (logger.isInfoEnabled()) {
558 			logger.info("updateUser(): " + update + "...");
559 		}
560 
561 		final UserInfo updated = privateUpdateUser(correlationId, user, username, update);
562 
563 		if (logger.isInfoEnabled()) {
564 			logger.info("updateUser(): Ending.");
565 		}
566 
567 		return updated;
568 	}
569 
570 	private static void assertRoleCanManageRole(final EnumRole manager, final Role managed)
571 			throws UnauthorizedException {
572 
573 		checkNotNull(manager, "manager");
574 		checkNotNull(managed, "managed");
575 
576 		if (!((Role) manager).canManage(managed)) {
577 
578 			throw new UnauthorizedException(
579 					"Role: " + manager.getRolename() + " cannot manage role: " + managed.getRolename());
580 		}
581 	}
582 
583 	public UserInfo privateUpdateUser(final String correlationId, final User user, final String username,
584 			final UserUpdate update) throws ServiceException {
585 
586 		@Nullable
587 		final String password = update.getPassword();
588 
589 		final Role oldRole = privateGetUser(username).getRole();
590 
591 		assertRoleCanManageRole(user.getRole(), oldRole);
592 
593 		final Role role = wrap(() -> extractUpdateRole(update, () -> oldRole));
594 
595 		assertRoleCanManageRole(user.getRole(), role);
596 
597 		final boolean enabled = wrap(() -> extractUpdateEnabled(update, () -> //
598 		usersDao.getUser(username).isEnabled()));
599 
600 		final UserDto toDto = wrap(() -> {
601 
602 			usersDao.updateUser(username, //
603 					role.getRolename(), //
604 					update.getPreferredLang(), //
605 					update.getPreferredTimeZone(), //
606 					enabled, //
607 					update.getFromRevision());
608 
609 			if (password != null) {
610 
611 				authDao.setUserPassword(username, password);
612 			}
613 
614 			return usersDao.getUser(username);
615 
616 		});
617 
618 		return dto2UserInfo(toDto);
619 	}
620 
621 	@Override
622 	public void deleteUser(final String correlationId, final User user, final String username) throws ServiceException {
623 
624 		checkNotNull(correlationId, "correlationId");
625 		checkNotNull(user, "user");
626 		checkNotNull(username, "username");
627 
628 		LogFactory.setCorrelationId(correlationId);
629 
630 		// 0. PERMISSIONS
631 
632 		permissions.assertAuthorized(correlationId, user);
633 
634 		// 1. LOGIC
635 
636 		privateDeleteUser(user, username);
637 	}
638 
639 	private void privateDeleteUser(final User user, final String username) throws ServiceException {
640 
641 		// final UserInfo old = getUser(correlationId, user, username);
642 		//
643 		// if (user.getRole() == Role.USERMGR && old.getRole() == Role.ADMIN) {
644 		// throw new UnauthorizedException("USERMGR cannot act on ADMINs");
645 		// }
646 
647 		assertNonSuperadminUsername(username);
648 
649 		if (user.getUsername().contentEquals(username)) {
650 			throw new UnauthorizedException("A user may not delete itself: " + username);
651 		}
652 
653 		wrap(()
654 
655 		-> usersDao.deleteUser(username));
656 	}
657 }