1 package net.sumaris.core.dao.administration.user;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import com.google.common.base.Preconditions;
26 import com.google.common.collect.ImmutableList;
27 import net.sumaris.core.dao.data.ImageAttachmentDao;
28 import net.sumaris.core.dao.referential.ReferentialDao;
29 import net.sumaris.core.dao.technical.SoftwareDao;
30 import net.sumaris.core.dao.technical.SortDirection;
31 import net.sumaris.core.dao.technical.hibernate.HibernateDaoSupport;
32 import net.sumaris.core.model.administration.user.Department;
33 import net.sumaris.core.model.administration.user.Person;
34 import net.sumaris.core.model.referential.IReferentialEntity;
35 import net.sumaris.core.model.referential.Status;
36 import net.sumaris.core.model.referential.UserProfile;
37 import net.sumaris.core.model.referential.UserProfileEnum;
38 import net.sumaris.core.util.Beans;
39 import net.sumaris.core.util.crypto.MD5Util;
40 import net.sumaris.core.vo.administration.user.DepartmentVO;
41 import net.sumaris.core.vo.administration.user.PersonVO;
42 import net.sumaris.core.vo.data.ImageAttachmentVO;
43 import net.sumaris.core.vo.filter.PersonFilterVO;
44 import net.sumaris.core.vo.referential.ReferentialVO;
45 import org.apache.commons.collections4.CollectionUtils;
46 import org.apache.commons.lang3.ArrayUtils;
47 import org.apache.commons.lang3.StringUtils;
48 import org.nuiton.i18n.I18n;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.beans.factory.annotation.Autowired;
52 import org.springframework.dao.DataRetrievalFailureException;
53 import org.springframework.dao.EmptyResultDataAccessException;
54 import org.springframework.stereotype.Repository;
55
56 import javax.persistence.EntityManager;
57 import javax.persistence.LockModeType;
58 import javax.persistence.NoResultException;
59 import javax.persistence.criteria.*;
60 import java.sql.Timestamp;
61 import java.util.*;
62 import java.util.concurrent.CopyOnWriteArrayList;
63 import java.util.regex.Matcher;
64 import java.util.regex.Pattern;
65 import java.util.stream.Collectors;
66
67 @Repository("personDao")
68 public class PersonDaoImpl extends HibernateDaoSupport implements PersonDao {
69
70
71 private static final Logger log =
72 LoggerFactory.getLogger(PersonDaoImpl.class);
73
74 private List<Listener> listeners = new CopyOnWriteArrayList<>();
75
76 @Autowired
77 private DepartmentDao departmentDao;
78
79 @Autowired
80 private ImageAttachmentDao imageAttachmentDao;
81
82 @Autowired
83 private SoftwareDao softwareDao;
84
85 @Autowired
86 private ReferentialDao referentialDao;
87
88 @Override
89 @SuppressWarnings("unchecked")
90 public List<PersonVO> findByFilter(PersonFilterVO filter, int offset, int size, String sortAttribute, SortDirection sortDirection) {
91 Preconditions.checkNotNull(filter);
92 Preconditions.checkArgument(offset >= 0);
93 Preconditions.checkArgument(size > 0);
94
95 EntityManager entityManager = getEntityManager();
96 CriteriaBuilder builder = entityManager.getCriteriaBuilder();
97 CriteriaQuery<Person> query = builder.createQuery(Person.class);
98 Root<Person> root = query.from(Person.class);
99 Join<Person, UserProfile> upJ = root.join(Person.Fields.USER_PROFILES, JoinType.LEFT);
100
101 ParameterExpression<Boolean> hasUserProfileIdsParam = builder.parameter(Boolean.class);
102 ParameterExpression<Collection> userProfileIdsParam = builder.parameter(Collection.class);
103 ParameterExpression<Boolean> hasStatusIdsParam = builder.parameter(Boolean.class);
104 ParameterExpression<Collection> statusIdsParam = builder.parameter(Collection.class);
105 ParameterExpression<String> pubkeyParam = builder.parameter(String.class);
106 ParameterExpression<String> firstNameParam = builder.parameter(String.class);
107 ParameterExpression<String> lastNameParam = builder.parameter(String.class);
108 ParameterExpression<String> emailParam = builder.parameter(String.class);
109 ParameterExpression<String> searchTextParam = builder.parameter(String.class);
110
111
112 Collection<Integer> statusIds = ArrayUtils.isEmpty(filter.getStatusIds()) ?
113 null : ImmutableList.copyOf(filter.getStatusIds());
114
115
116 Collection<Integer> userProfileIds;
117 if (ArrayUtils.isNotEmpty(filter.getUserProfiles())) {
118 userProfileIds = Arrays.stream(filter.getUserProfiles())
119 .map(UserProfileEnum::valueOf)
120 .map(profile -> profile.id)
121 .collect(Collectors.toList());
122 }
123 else if (ArrayUtils.isNotEmpty(filter.getUserProfileIds())) {
124 userProfileIds = ImmutableList.copyOf(filter.getUserProfileIds());
125 }
126 else if (filter.getUserProfileId() != null) {
127 userProfileIds = ImmutableList.of(filter.getUserProfileId());
128 }
129 else {
130 userProfileIds = null;
131 }
132
133 query.select(root).distinct(true)
134 .where(
135 builder.and(
136
137 builder.or(
138 builder.isFalse(hasUserProfileIdsParam),
139 upJ.get(UserProfile.Fields.ID).in(userProfileIdsParam)
140 ),
141
142 builder.or(
143 builder.isFalse(hasStatusIdsParam),
144 root.get(Person.Fields.STATUS).get(Status.Fields.ID).in(statusIdsParam)
145 ),
146
147 builder.or(
148 builder.isNull(pubkeyParam),
149 builder.equal(root.get(Person.Fields.PUBKEY), pubkeyParam)
150 ),
151
152 builder.or(
153 builder.isNull(emailParam),
154 builder.equal(root.get(Person.Fields.EMAIL), emailParam)
155 ),
156
157 builder.or(
158 builder.isNull(firstNameParam),
159 builder.equal(builder.upper(root.get(Person.Fields.FIRST_NAME)), builder.upper(firstNameParam))
160 ),
161
162 builder.or(
163 builder.isNull(lastNameParam),
164 builder.equal(builder.upper(root.get(Person.Fields.LAST_NAME)), builder.upper(lastNameParam))
165 ),
166
167 builder.or(
168 builder.isNull(searchTextParam),
169 builder.like(builder.upper(root.get(Person.Fields.PUBKEY)), builder.upper(searchTextParam)),
170 builder.like(builder.upper(root.get(Person.Fields.EMAIL)), builder.upper(searchTextParam)),
171 builder.like(builder.upper(root.get(Person.Fields.FIRST_NAME)), builder.upper(searchTextParam)),
172 builder.like(builder.upper(root.get(Person.Fields.LAST_NAME)), builder.upper(searchTextParam))
173 )
174 ));
175 if (StringUtils.isNotBlank(sortAttribute)) {
176 if (sortDirection == SortDirection.ASC) {
177 query.orderBy(builder.asc(root.get(sortAttribute)));
178 } else {
179 query.orderBy(builder.desc(root.get(sortAttribute)));
180 }
181 }
182
183 String searchText = StringUtils.trimToNull(filter.getSearchText());
184 String searchTextAnyMatch = null;
185 if (StringUtils.isNotBlank(searchText)) {
186 searchTextAnyMatch = ("*" + searchText + "*");
187 searchTextAnyMatch = searchTextAnyMatch.replaceAll("[*]+", "*");
188 searchTextAnyMatch = searchTextAnyMatch.replaceAll("[%]", "\\%");
189 searchTextAnyMatch = searchTextAnyMatch.replaceAll("[*]", "%");
190 }
191
192
193 return entityManager.createQuery(query)
194 .setParameter(hasUserProfileIdsParam, CollectionUtils.isNotEmpty(userProfileIds))
195 .setParameter(userProfileIdsParam, userProfileIds)
196 .setParameter(hasStatusIdsParam, CollectionUtils.isNotEmpty(statusIds))
197 .setParameter(statusIdsParam, statusIds)
198 .setParameter(pubkeyParam, filter.getPubkey())
199 .setParameter(emailParam, filter.getEmail())
200 .setParameter(firstNameParam, filter.getFirstName())
201 .setParameter(lastNameParam, filter.getLastName())
202 .setParameter(searchTextParam, searchTextAnyMatch)
203 .setFirstResult(offset)
204 .setMaxResults(size)
205 .getResultList()
206 .stream()
207 .map(this::toPersonVO)
208 .collect(Collectors.toList());
209 }
210
211
212 @Override
213 public Long countByFilter(PersonFilterVO filter) {
214 Preconditions.checkNotNull(filter);
215
216 List<Integer> statusIds = ArrayUtils.isEmpty(filter.getStatusIds()) ?
217 null : ImmutableList.copyOf(filter.getStatusIds());
218
219 return getEntityManager().createNamedQuery("Person.count", Long.class)
220 .setParameter("userProfileId", filter.getUserProfileId())
221 .setParameter("statusIds", statusIds)
222 .setParameter("email", StringUtils.trimToNull(filter.getEmail()))
223 .setParameter("pubkey", StringUtils.trimToNull(filter.getPubkey()))
224 .setParameter("firstName", StringUtils.trimToNull(filter.getFirstName()))
225 .setParameter("lastName", StringUtils.trimToNull(filter.getLastName()))
226 .getSingleResult();
227 }
228
229 @Override
230 public PersonVO getByPubkeyOrNull(String pubkey) {
231 return toPersonVO(getEntityByPubkeyOrNull(pubkey));
232 }
233
234 @Override
235 public ImageAttachmentVO getAvatarByPubkey(String pubkey) {
236
237 Person person = getEntityByPubkeyOrNull(pubkey);
238 if (person == null || person.getAvatar() == null) {
239 throw new DataRetrievalFailureException(I18n.t("sumaris.error.person.avatar.notFound"));
240 }
241
242 return imageAttachmentDao.get(person.getAvatar().getId());
243 }
244
245 @Override
246 public List<String> getEmailsByProfiles(List<Integer> userProfiles, List<Integer> statusIds) {
247 Preconditions.checkNotNull(userProfiles);
248 Preconditions.checkArgument(CollectionUtils.isNotEmpty(userProfiles));
249 Preconditions.checkNotNull(statusIds);
250 Preconditions.checkArgument(CollectionUtils.isNotEmpty(statusIds));
251
252 EntityManager entityManager = getEntityManager();
253 CriteriaBuilder builder = entityManager.getCriteriaBuilder();
254 CriteriaQuery<Person> query = builder.createQuery(Person.class);
255 Root<Person> root = query.from(Person.class);
256
257 Join<Person, UserProfile> upJ = root.join(Person.Fields.USER_PROFILES, JoinType.INNER);
258
259 ParameterExpression<Collection> userProfileIdParam = builder.parameter(Collection.class);
260 ParameterExpression<Collection> statusIdsParam = builder.parameter(Collection.class);
261
262 query.select(root).distinct(true)
263 .where(
264 builder.and(
265
266 upJ.get(UserProfile.Fields.ID).in(userProfileIdParam),
267
268 root.get(Person.Fields.STATUS).get(Status.Fields.ID).in(statusIdsParam)
269 ));
270
271
272 return entityManager.createQuery(query)
273 .setParameter(userProfileIdParam, userProfiles)
274 .setParameter(statusIdsParam, ImmutableList.copyOf(statusIds))
275 .setMaxResults(100)
276 .getResultList()
277 .stream()
278 .map(Person::getEmail)
279 .filter(Objects::nonNull)
280 .collect(Collectors.toList());
281 }
282
283 @Override
284 public boolean isExistsByEmailHash(final String hash) {
285
286 CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
287 CriteriaQuery<Long> query = builder.createQuery(Long.class);
288 Root<Person> root = query.from(Person.class);
289
290 ParameterExpression<String> hashParam = builder.parameter(String.class);
291
292 query.select(builder.count(root.get(Person.Fields.ID)))
293 .where(builder.equal(root.get(Person.Fields.EMAIL_M_D5), hashParam));
294
295 return getEntityManager().createQuery(query)
296 .setParameter(hashParam, hash)
297 .getSingleResult() > 0;
298 }
299
300 @Override
301 public PersonVO get(int id) {
302 return toPersonVO(get(Person.class, id));
303 }
304
305 @Override
306 public PersonVOf="../../../../../../net/sumaris/core/vo/administration/user/PersonVO.html#PersonVO">PersonVO save(PersonVO source) {
307 Preconditions.checkNotNull(source);
308 Preconditions.checkNotNull(source.getEmail(), "Missing 'email'");
309 Preconditions.checkNotNull(source.getStatusId(), "Missing 'statusId'");
310 Preconditions.checkNotNull(source.getDepartment(), "Missing 'department'");
311 Preconditions.checkNotNull(source.getDepartment().getId(), "Missing 'department.id'");
312
313 EntityManager entityManager = getEntityManager();
314 Person entity = null;
315 if (source.getId() != null) {
316 entity = get(Person.class, source.getId());
317 }
318 boolean isNew = (entity == null);
319 if (isNew) {
320 entity = new Person();
321 }
322
323
324 if (isNew) {
325
326 if (source.getStatusId() == null) {
327 source.setStatusId(config.getStatusIdTemporary());
328 }
329 }
330
331 else {
332
333
334 checkUpdateDateForUpdate(source, entity);
335
336
337 lockForUpdate(entity, LockModeType.PESSIMISTIC_WRITE);
338 }
339
340 personVOToEntity(source, entity, true);
341
342
343 Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
344 entity.setUpdateDate(newUpdateDate);
345
346
347 if (isNew) {
348
349 entity.setCreationDate(newUpdateDate);
350 source.setCreationDate(newUpdateDate);
351
352 entityManager.persist(entity);
353 source.setId(entity.getId());
354 } else {
355 entityManager.merge(entity);
356 }
357
358 source.setUpdateDate(newUpdateDate);
359
360 getEntityManager().flush();
361 getEntityManager().clear();
362
363
364 emitSaveEvent(source);
365
366 return source;
367 }
368
369 @Override
370 public void delete(int id) {
371 log.debug(String.format("Deleting person {id=%s}...", id));
372 delete(Person.class, id);
373
374
375 emitDeleteEvent(id);
376 }
377
378 @Override
379 public PersonVO toPersonVO(Person source) {
380 if (source == null) return null;
381 PersonVOnistration/user/PersonVO.html#PersonVO">PersonVO target = new PersonVO();
382
383 Beans.copyProperties(source, target);
384
385
386 DepartmentVO department = departmentDao.get(source.getDepartment().getId());
387 target.setDepartment(department);
388
389
390 target.setStatusId(source.getStatus().getId());
391
392
393 if (CollectionUtils.isNotEmpty(source.getUserProfiles())) {
394 List<String> profiles = source.getUserProfiles().stream()
395 .map(profile -> getUserProfileLabelTranslationMap(true).getOrDefault(profile.getLabel(), profile.getLabel()))
396 .collect(Collectors.toList());
397 target.setProfiles(profiles);
398 }
399
400
401 target.setHasAvatar(source.getAvatar() != null);
402
403 return target;
404 }
405
406 @Override
407 public void addListener(Listener listener) {
408 if (!listeners.contains(listener)) {
409 listeners.add(listener);
410 }
411 }
412
413
414
415
416 protected List<PersonVO> toPersonVOs(List<Person> source) {
417 return source.stream()
418 .map(this::toPersonVO)
419 .filter(Objects::nonNull)
420 .collect(Collectors.toList());
421 }
422
423 protected void personVOToEntity(PersonVO source, Person target, boolean copyIfNull) {
424
425 Beans.copyProperties(source, target);
426
427
428 if (StringUtils.isNotBlank(source.getEmail())) {
429 target.setEmailMD5(MD5Util.md5Hex(source.getEmail()));
430 }
431
432
433 if (copyIfNull || source.getDepartment() != null) {
434 if (source.getDepartment() == null) {
435 target.setDepartment(null);
436 }
437 else {
438 target.setDepartment(load(Department.class, source.getDepartment().getId()));
439 }
440 }
441
442
443 if (copyIfNull || source.getStatusId() != null) {
444 if (source.getStatusId() == null) {
445 target.setStatus(null);
446 }
447 else {
448 target.setStatus(load(Status.class, source.getStatusId()));
449 }
450 }
451
452
453 if (copyIfNull || CollectionUtils.isNotEmpty(source.getProfiles())) {
454 if (CollectionUtils.isEmpty(source.getProfiles())) {
455 target.getUserProfiles().clear();
456 }
457 else {
458 target.getUserProfiles().clear();
459 for (String profile: source.getProfiles()) {
460 if (StringUtils.isNotBlank(profile)) {
461
462 String translatedLabel = getUserProfileLabelTranslationMap(false).getOrDefault(profile, profile);
463 if (StringUtils.isNotBlank(translatedLabel)) {
464 ReferentialVO userProfileVO = referentialDao.findByUniqueLabel("UserProfile", translatedLabel);
465 UserProfile up = load(UserProfile.class, userProfileVO.getId());
466 target.getUserProfiles().add(up);
467 }
468 }
469 }
470 }
471 }
472
473 }
474
475
476 protected Person getEntityByPubkeyOrNull(String pubkey) {
477
478 EntityManager entityManager = getEntityManager();
479 CriteriaBuilder builder = entityManager.getCriteriaBuilder();
480 CriteriaQuery<Person> query = builder.createQuery(Person.class);
481 Root<Person> root = query.from(Person.class);
482
483 ParameterExpression<String> pubkeyParam = builder.parameter(String.class);
484
485 query.select(root)
486 .where(builder.equal(root.get(PersonVO.Fields.PUBKEY), pubkeyParam));
487
488 try {
489 return entityManager.createQuery(query)
490 .setParameter(pubkeyParam, pubkey)
491 .getSingleResult();
492 } catch (EmptyResultDataAccessException | NoResultException e) {
493 return null;
494 }
495 }
496
497 protected void emitSaveEvent(final PersonVO person) {
498 listeners.forEach(l -> {
499 try {
500 l.onSave(person);
501 } catch(Throwable t) {
502 log.error("Person listener (onSave) error: " + t.getMessage(), t);
503
504 }
505 });
506 }
507
508 protected void emitDeleteEvent(final int id) {
509 listeners.forEach(l -> {
510 try {
511 l.onDelete(id);
512 } catch(Throwable t) {
513 log.error("Person listener (onDelete) error: " + t.getMessage(), t);
514
515 }
516 });
517 }
518
519
520
521
522
523
524
525 private Map<String, String> getUserProfileLabelTranslationMap(boolean toVO) {
526 Map<String, String> translateMap = new HashMap<>();
527 Pattern pattern = Pattern.compile("sumaris.userProfile.(\\w+).label");
528 softwareDao.get(config.getAppName()).getProperties().forEach((key, value) -> {
529 Matcher matcher = pattern.matcher(key);
530 if (value != null && matcher.find()) {
531 if (toVO)
532 translateMap.put(value, matcher.group(1));
533 else
534 translateMap.put(matcher.group(1), value);
535 }
536 });
537 return translateMap;
538 }
539 }