ref #7620 filtered registration search implemented with filter string for references
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / registration / RegistrationWorkingSetService.java
1 /**
2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.cdm.api.service.registration;
10
11 import java.io.IOException;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Optional;
18 import java.util.Set;
19 import java.util.UUID;
20
21 import org.apache.log4j.Logger;
22 import org.hibernate.Hibernate;
23 import org.joda.time.DateTime;
24 import org.joda.time.Partial;
25 import org.springframework.beans.factory.annotation.Autowired;
26 import org.springframework.beans.factory.annotation.Qualifier;
27 import org.springframework.stereotype.Service;
28 import org.springframework.transaction.annotation.Transactional;
29
30 import eu.etaxonomy.cdm.api.application.CdmRepository;
31 import eu.etaxonomy.cdm.api.service.dto.RegistrationDTO;
32 import eu.etaxonomy.cdm.api.service.dto.RegistrationWorkingSet;
33 import eu.etaxonomy.cdm.api.service.exception.RegistrationValidationException;
34 import eu.etaxonomy.cdm.api.service.pager.Pager;
35 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
36 import eu.etaxonomy.cdm.api.utility.UserHelper;
37 import eu.etaxonomy.cdm.database.PermissionDeniedException;
38 import eu.etaxonomy.cdm.format.ReferenceEllypsisFormatter;
39 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
40 import eu.etaxonomy.cdm.model.common.User;
41 import eu.etaxonomy.cdm.model.name.Registration;
42 import eu.etaxonomy.cdm.model.name.RegistrationStatus;
43 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
44 import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
45 import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
46 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
47 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
48 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
49 import eu.etaxonomy.cdm.model.reference.Reference;
50 import eu.etaxonomy.cdm.model.reference.ReferenceType;
51 import eu.etaxonomy.cdm.persistence.dao.initializer.EntityInitStrategy;
52 import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
53 import eu.etaxonomy.cdm.persistence.hibernate.permission.CRUD;
54 import eu.etaxonomy.cdm.persistence.query.MatchMode;
55 import eu.etaxonomy.cdm.persistence.query.OrderHint;
56 import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
57
58 /**
59 * Provides RegistrationDTOs and RegistrationWorkingsets for Registrations in the database.
60 *
61 *
62 * @author a.kohlbecker
63 * @since Mar 10, 2017
64 *
65 */
66 @Service("registrationWorkingSetService")
67 @Transactional(readOnly=true)
68 public class RegistrationWorkingSetService implements IRegistrationWorkingSetService {
69
70 public static final EntityInitStrategy REGISTRATION_DTO_INIT_STRATEGY = new EntityInitStrategy(Arrays.asList(new String []{
71 "blockedBy",
72 // typeDesignation
73 "typeDesignations.typeStatus",
74 "typeDesignations.typifiedNames.typeDesignations", // important !!
75 "typeDesignations.typeSpecimen",
76 "typeDesignations.typeName.$",
77 "typeDesignations.citation",
78 "typeDesignations.annotations", // needed for AnnotatableEntity.clone() in DerivedUnitConverter.copyPropertiesTo
79 "typeDesignations.markers", // needed for AnnotatableEntity.clone() in DerivedUnitConverter.copyPropertiesTo
80 "typeDesignations.registrations", // DerivedUnitConverter.copyPropertiesTo(TARGET n)
81
82 // name
83 "name.$",
84 "name.nomenclaturalReference",
85 "name.rank",
86 "name.homotypicalGroup.typifiedNames",
87 "name.status.type",
88 "name.typeDesignations", // important !!"
89 // institution
90 "institution",
91 }
92 ))
93 .extend("typeDesignations.citation", ReferenceEllypsisFormatter.INIT_STRATEGY, false)
94 .extend("name.nomenclaturalReference", ReferenceEllypsisFormatter.INIT_STRATEGY, false);
95
96 public EntityInitStrategy DERIVEDUNIT_INIT_STRATEGY = new EntityInitStrategy(Arrays.asList(new String[]{
97 "*", // initialize all related entities to allow DerivedUnit conversion, see DerivedUnitConverter.copyPropertiesTo()
98 "derivedFrom.$",
99 "derivedFrom.type", // TODO remove?
100 "derivedFrom.originals.derivationEvents", // important!!
101 "specimenTypeDesignations.typifiedNames.typeDesignations", // important!!
102 "mediaSpecimen.sources.citation",
103 "collection.institute"// see CollectionCaptionGenerator
104 })).extend("mediaSpecimen.sources.citation", ReferenceEllypsisFormatter.INIT_STRATEGY, false);
105
106 public List<String> FIELDUNIT_INIT_STRATEGY = Arrays.asList(new String[]{
107 "$",
108 "annotations.*", // * is needed as log as we are using a table in FilterableAnnotationsField
109 "gatheringEvent.$",
110 "gatheringEvent.country",
111 "gatheringEvent.collectingAreas",
112 "gatheringEvent.actor",
113 "gatheringEvent.exactLocation.$",
114 "derivationEvents.derivatives", // important, otherwise the DerivedUnits are not included into the graph of initialized entities!!!
115
116 });
117
118 public static final List<String> BLOCKING_REGISTRATION_INIT_STRATEGY = Arrays.asList(new String []{
119
120 "blockedBy.blockedBy",
121 // typeDesignation
122 "blockedBy.typeDesignations.typeStatus",
123 // "typeDesignations.typifiedNames.typeDesignations", // important !?
124 // "blockedBy.name.$",
125 "blockedBy.name.nomenclaturalReference.authorship",
126 "blockedBy.name.nomenclaturalReference.inReference",
127 "blockedBy.name.rank",
128 // institution
129 "blockedBy.institution",
130 }
131 );
132
133 /**
134 *
135 */
136 private static final int PAGE_SIZE = 50;
137
138 private static final Logger logger = Logger.getLogger(RegistrationWorkingSetService.class);
139
140 @Autowired
141 @Qualifier("cdmRepository")
142 private CdmRepository repo;
143
144 @Autowired
145 private UserHelper userHelper;
146
147 @Autowired
148 protected IBeanInitializer defaultBeanInitializer;
149
150 public RegistrationWorkingSetService() {
151
152 }
153
154
155 /**
156 * @param id the Registration entity id
157 * @return
158 */
159 @Override
160 public RegistrationDTO loadDtoById(Integer id) {
161 Registration reg = repo.getRegistrationService().load(id, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
162 inititializeSpecimen(reg);
163 return new RegistrationDTO(reg);
164 }
165
166
167 /**
168 * @param id the Registration entity id
169 * @return
170 */
171 @Override
172 public RegistrationDTO loadDtoByUuid(UUID uuid) {
173 Registration reg = repo.getRegistrationService().load(uuid, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
174 inititializeSpecimen(reg);
175 return new RegistrationDTO(reg);
176 }
177
178 @Override
179 public Pager<RegistrationDTO> pageDTOs(String identifier, Integer pageIndex, Integer pageSize) throws IOException {
180
181 Pager<Registration> regPager = repo.getRegistrationService().pageByIdentifier(identifier, pageIndex, pageSize, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
182 return convertToDTOPager(regPager);
183 }
184
185
186 /**
187 * @param regPager
188 * @return
189 */
190 @Override
191 public Pager<RegistrationDTO> convertToDTOPager(Pager<Registration> regPager) {
192 return new DefaultPagerImpl<RegistrationDTO>(regPager.getCurrentIndex(), regPager.getCount(), regPager.getPageSize(), makeDTOs(regPager.getRecords()));
193 }
194
195
196 @Override
197 public Pager<RegistrationDTO> pageDTOs(Integer pageSize, Integer pageIndex) {
198
199 return pageDTOs((User)null, null, null, null, null, pageSize, pageIndex, null);
200 }
201
202 /**
203 * {@inheritDoc}
204 */
205 @Override
206 public Pager<RegistrationDTO> pageDTOs(User submitter, Collection<RegistrationStatus> includedStatus,
207 String identifierFilterPattern, String taxonNameFilterPattern, Set<TypeDesignationStatusBase> typeStatusFilter,
208 Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
209
210 if(pageSize == null){
211 pageSize = PAGE_SIZE;
212 }
213
214 if(orderHints == null){
215 orderHints = Arrays.asList(new OrderHint("identifier", SortOrder.ASCENDING));
216 }
217
218 Pager<Registration> pager = repo.getRegistrationService().page(submitter, includedStatus, identifierFilterPattern, taxonNameFilterPattern,
219 typeStatusFilter, PAGE_SIZE, pageIndex , orderHints, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
220
221 Pager<RegistrationDTO> dtoPager = convertToDTOPager(pager);
222 if(logger.isDebugEnabled()){
223 logger.debug(String.format("pageDTOs() pageIndex: $1%d, pageSize: $2%d, includedStatus: $3%s, identifierFilterPattern: $4%s, taxonNameFilterPattern: $5%s, submitter: $6%s",
224 pageIndex, pageSize, includedStatus, identifierFilterPattern, taxonNameFilterPattern, submitter));
225 logger.debug("pageDTOs() result: " + pager.toString());
226 }
227 return dtoPager;
228 }
229
230 /**
231 * @param submitterUuid
232 * Filter by the uuid of the {@link User} associated with the Registration as <code>Registration.submitter</code>
233 * @param includedStatus
234 * Filter by one or more {@link RegistrationStatus}. Multiple status will be combined with OR. In case the current user
235 * is not authenticated (i.e. the authentication is anonymous) the includedStatus will be set to {@link RegistrationStatus#PUBLISHED}
236 * to protect all other Registrations from being undisclosed.
237 * @param identifierFilterPattern
238 * Filter by the {@link Registration#getIdentifier() Registration.identifier}.
239 * The method matches Registrations which contain the the passed pattern in the identifier.
240 * @param taxonNameFilterPattern
241 * The method matches Registrations which contain the the passed pattern in the
242 * {@link Registration#getName() Registration.name}
243 * @param typeDesignationStatusUuids
244 * Filter by one or more {@link eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus} or {@link eu.etaxonomy.cdm.model.name.NameTypeDesignationStatus}.
245 * Multiple status will be combined with OR.
246 * @param pageSize
247 * @param pageIndex
248 * @param orderHints
249 * @return
250 */
251 @Override
252 public Pager<RegistrationDTO> pageDTOs(UUID submitterUuid, Collection<RegistrationStatus> includedStatus, String identifierFilterPattern,
253 String taxonNameFilterPattern, String referenceFilterPattern, Collection<UUID> typeDesignationStatusUuids,
254 Integer pageSize, Integer pageIndex, List<OrderHint> orderHints){
255
256 if(pageSize == null){
257 pageSize = PAGE_SIZE;
258 }
259
260 if(orderHints == null){
261 orderHints = Arrays.asList(new OrderHint("identifier", SortOrder.ASCENDING));
262 }
263
264 Pager<Registration> pager = repo.getRegistrationService().page(submitterUuid, includedStatus,
265 identifierFilterPattern, taxonNameFilterPattern,
266 referenceFilterPattern, typeDesignationStatusUuids, PAGE_SIZE , pageIndex, orderHints, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
267
268 Pager<RegistrationDTO> dtoPager = convertToDTOPager(pager);
269 if(logger.isDebugEnabled()){
270 logger.debug(String.format("pageDTOs() pageIndex: $1%d, pageSize: $2%d, includedStatusUuids: $3%s, typeDesignationStatusUuids: $4%s, taxonNameFilterPattern: $5%s, submitterUuid: $6%s",
271 pageIndex, pageSize, includedStatus, identifierFilterPattern, taxonNameFilterPattern, submitterUuid));
272 logger.debug("pageDTOs() result: " + pager.toString());
273 }
274 return dtoPager;
275
276 }
277
278 @Override
279 public Pager<RegistrationDTO> findInTaxonGraph(UUID submitterUuid, Collection<RegistrationStatus> includedStatus,
280 String taxonNameFilterPattern, MatchMode matchMode,
281 Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
282
283 Pager<Registration> regPager = repo.getRegistrationService().pageTaxomicInclusion(null, includedStatus,
284 taxonNameFilterPattern, matchMode,
285 pageSize, pageIndex, orderHints, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
286
287 return convertToDTOPager(regPager);
288 }
289
290
291 /**
292 * {@inheritDoc}
293 * @throws RegistrationValidationException
294 */
295 @Override
296 public RegistrationWorkingSet loadWorkingSetByReferenceUuid(UUID referenceUuid, boolean resolveSections) throws RegistrationValidationException, PermissionDeniedException {
297
298 Reference reference = repo.getReferenceService().load(referenceUuid); // needed to use load to avoid the problem described in #7331
299 if(resolveSections){
300 reference = resolveSection(reference);
301 }
302
303 checkPermissions(reference);
304
305 Pager<Registration> pager = repo.getRegistrationService().page(Optional.of(reference), null, null, null, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
306
307 /* for debugging https://dev.e-taxonomy.eu/redmine/issues/7331 */
308 // debugIssue7331(pager);
309 RegistrationWorkingSet registrationWorkingSet;
310 if(pager.getCount() > 0) {
311 registrationWorkingSet = new RegistrationWorkingSet(makeDTOs(pager.getRecords()));
312 } else {
313 registrationWorkingSet = new RegistrationWorkingSet(reference);
314 }
315 return registrationWorkingSet;
316 }
317
318
319 /**
320 * @param reference
321 */
322 private void checkPermissions(Reference reference) throws PermissionDeniedException {
323
324 boolean permissionDenied = isPermissionDenied(reference);
325 if(permissionDenied) {
326 throw new PermissionDeniedException("Access to the workingset is denied for the current user.");
327 }
328 }
329
330
331 /**
332 * @param reference
333 * @return
334 */
335 public boolean isPermissionDenied(Reference reference) {
336 boolean permissionDenied = false;
337 if(!checkReferencePublished(reference)){
338 permissionDenied = !userHelper.userHasPermission(reference, CRUD.UPDATE);
339 }
340 return permissionDenied;
341 }
342
343
344 /**
345 * @param reference
346 * @return
347 */
348 public boolean checkReferencePublished(Reference reference) {
349
350 if(reference.getDatePublished() == null){
351 return false;
352 }
353 Partial pubPartial = null;
354 if(reference.getDatePublished().getStart() != null){
355 pubPartial = reference.getDatePublished().getStart();
356 } else {
357 pubPartial = reference.getDatePublished().getEnd();
358 }
359 if(pubPartial == null){
360 return !reference.getDatePublished().getFreeText().isEmpty();
361 }
362
363 DateTime nowLocal = new DateTime();
364 //LocalDateTime nowUTC = nowLocal.withZone(DateTimeZone.UTC).toLocalDateTime();
365
366 DateTime pubDateTime = pubPartial.toDateTime(null);
367 return nowLocal.isAfter(pubDateTime);
368
369 }
370
371
372 /**
373 * @param reference
374 * @return
375 */
376 protected Reference resolveSection(Reference reference) {
377 repo.getReferenceService().load(reference.getUuid(), Arrays.asList(new String[]{"inReference"})); // needed to avoid the problem described in #7331
378 if(reference.isOfType(ReferenceType.Section) && reference.getInReference() != null) {
379 reference = reference.getInReference();
380 }
381 return reference;
382 }
383
384 /**
385 * {@inheritDoc}
386 * @throws RegistrationValidationException
387 */
388 @Override
389 public RegistrationWorkingSet loadWorkingSetByReferenceID(Integer referenceID, boolean resolveSections) throws RegistrationValidationException, PermissionDeniedException {
390
391 Reference reference = repo.getReferenceService().find(referenceID);
392 if(resolveSections){
393 reference = resolveSection(reference);
394 }
395
396 checkPermissions(reference);
397
398 repo.getReferenceService().load(reference.getUuid()); // needed to avoid the problem described in #7331
399
400 Pager<Registration> pager = repo.getRegistrationService().page(Optional.of(reference), null, null, null, REGISTRATION_DTO_INIT_STRATEGY.getPropertyPaths());
401
402 /* for debugging https://dev.e-taxonomy.eu/redmine/issues/7331 */
403 // debugIssue7331(pager);
404
405 return new RegistrationWorkingSet(makeDTOs(pager.getRecords()));
406 }
407
408
409 /**
410 * @param pager
411 */
412 @SuppressWarnings("unused")
413 private void debugIssue7331(Pager<Registration> pager) {
414 for(Registration reg : pager.getRecords()){
415 if(reg.getName() != null && reg.getName().getNomenclaturalReference().getAuthorship() != null){
416 Reference ref = reg.getName().getNomenclaturalReference();
417 if(!Hibernate.isInitialized(ref.getAuthorship())){
418 logger.error("UNINITIALIZED");
419 }
420 } else {
421 logger.debug("NO AUTHORS");
422 }
423 }
424 }
425
426 @Override
427 public Set<RegistrationDTO> loadBlockingRegistrations(UUID blockedRegistrationUuid){
428
429 Registration registration = repo.getRegistrationService().load(blockedRegistrationUuid, BLOCKING_REGISTRATION_INIT_STRATEGY);
430 Set<Registration> registrations = registration.getBlockedBy();
431
432 Set<RegistrationDTO> blockingSet = new HashSet<>();
433 for(Registration reg : registrations){
434 blockingSet.add(new RegistrationDTO(reg));
435 }
436 return blockingSet;
437 }
438
439 /**
440 * @param regs
441 * @return
442 */
443 @Override
444 public List<RegistrationDTO> makeDTOs(Collection<Registration> regs) {
445 initializeSpecimens(regs);
446 List<RegistrationDTO> dtos = new ArrayList<>(regs.size());
447 regs.forEach(reg -> {dtos.add(new RegistrationDTO(reg));});
448 return dtos;
449 }
450
451
452 /**
453 * @param regs
454 */
455 public void initializeSpecimens(Collection<Registration> regs) {
456 for(Registration reg : regs){
457 inititializeSpecimen(reg);
458 }
459
460 }
461
462
463 /**
464 * @param reg
465 */
466 public void inititializeSpecimen(Registration reg) {
467
468 for(TypeDesignationBase<?> td : reg.getTypeDesignations()){
469 if(td instanceof SpecimenTypeDesignation){
470
471 DerivedUnit derivedUnit = ((SpecimenTypeDesignation) td).getTypeSpecimen();
472 @SuppressWarnings("rawtypes")
473 Set<SpecimenOrObservationBase> sobs = new HashSet<>();
474 sobs.add(HibernateProxyHelper.deproxy(derivedUnit));
475
476 while(sobs != null && !sobs.isEmpty()){
477 @SuppressWarnings("rawtypes")
478 Set<SpecimenOrObservationBase> nextSobs = null;
479 for(@SuppressWarnings("rawtypes") SpecimenOrObservationBase sob : sobs){
480 sob = HibernateProxyHelper.deproxy(sob);
481 if(sob == null){
482 continue;
483 }
484 if(DerivedUnit.class.isAssignableFrom(sob.getClass())) {
485 defaultBeanInitializer.initialize(sob, DERIVEDUNIT_INIT_STRATEGY.getPropertyPaths());
486 nextSobs = ((DerivedUnit)sob).getOriginals();
487 }
488 if(sob instanceof FieldUnit){
489 defaultBeanInitializer.initialize(sob, FIELDUNIT_INIT_STRATEGY);
490 }
491 }
492 sobs = nextSobs;
493 }
494 }
495 }
496 }
497
498
499
500
501
502
503 }