View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.backend.jpa.objectinfo;
20  
21  import java.sql.Date;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.Optional;
26  import java.util.stream.Collectors;
27  
28  import org.mycore.backend.jpa.MCREntityManagerProvider;
29  import org.mycore.datamodel.classifications2.MCRCategLinkReference_;
30  import org.mycore.datamodel.classifications2.MCRCategoryID;
31  import org.mycore.datamodel.classifications2.MCRCategoryID_;
32  import org.mycore.datamodel.classifications2.impl.MCRCategoryImpl;
33  import org.mycore.datamodel.classifications2.impl.MCRCategoryImpl_;
34  import org.mycore.datamodel.classifications2.impl.MCRCategoryLinkImpl;
35  import org.mycore.datamodel.classifications2.impl.MCRCategoryLinkImpl_;
36  import org.mycore.datamodel.common.MCRObjectIDDate;
37  import org.mycore.datamodel.ifs2.MCRObjectIDDateImpl;
38  import org.mycore.datamodel.metadata.MCRObjectID;
39  import org.mycore.datamodel.objectinfo.MCRObjectInfo;
40  import org.mycore.datamodel.objectinfo.MCRObjectQuery;
41  import org.mycore.datamodel.objectinfo.MCRObjectQueryResolver;
42  
43  import jakarta.persistence.EntityManager;
44  import jakarta.persistence.TypedQuery;
45  import jakarta.persistence.criteria.CriteriaBuilder;
46  import jakarta.persistence.criteria.CriteriaQuery;
47  import jakarta.persistence.criteria.Predicate;
48  import jakarta.persistence.criteria.Root;
49  import jakarta.persistence.metamodel.SingularAttribute;
50  
51  public class MCRObjectInfoEntityQueryResolver implements MCRObjectQueryResolver {
52  
53      private static final MCRObjectQuery.SortBy SORT_BY_DEFAULT = MCRObjectQuery.SortBy.created;
54  
55      protected TypedQuery<MCRObjectInfoEntity> convertQuery(MCRObjectQuery query) {
56          Objects.requireNonNull(query, "The Query cant be null");
57          int offset = query.offset();
58  
59          if (offset != -1 && query.afterId() != null) {
60              throw new IllegalArgumentException("offset and after_id should not be combined!");
61          }
62  
63          EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
64          CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
65          CriteriaQuery<MCRObjectInfoEntity> criteriaQuery = criteriaBuilder.createQuery(MCRObjectInfoEntity.class);
66          Root<MCRObjectInfoEntity> oe = criteriaQuery.from(MCRObjectInfoEntity.class);
67          criteriaQuery.select(oe);
68  
69          List<Predicate> filters = getFilter(query, criteriaBuilder, oe);
70  
71          applyClassificationFilter(query, criteriaBuilder, criteriaQuery, oe, filters, em);
72  
73          if (query.afterId() != null) {
74              applyLastId(query, criteriaBuilder, criteriaQuery, oe, filters);
75          } else {
76              applySort(query, criteriaBuilder, criteriaQuery, oe);
77          }
78  
79          if (filters.size() > 0) {
80              criteriaQuery.where(criteriaBuilder.and(filters.toArray(new Predicate[0])));
81          }
82  
83          TypedQuery<MCRObjectInfoEntity> typedQuery = em.createQuery(criteriaQuery);
84  
85          if (offset != -1) {
86              typedQuery.setFirstResult(offset);
87          }
88  
89          int limit = query.limit();
90          if (limit != -1) {
91              typedQuery.setMaxResults(limit);
92          }
93  
94          return typedQuery;
95      }
96  
97      private <T> void applyClassificationFilter(MCRObjectQuery query, CriteriaBuilder criteriaBuilder,
98          CriteriaQuery<T> criteriaQuery, Root<MCRObjectInfoEntity> oe, List<Predicate> filters, EntityManager em) {
99          if (query.getIncludeCategories().size() > 0) {
100             List<MCRCategoryImpl> idImplMap = getCategories(query, em);
101 
102             if (idImplMap.size() != query.getIncludeCategories().size()) {
103                 throw new IllegalArgumentException(
104                     "some of " + String.join(", ", query.getIncludeCategories()) + " do not exist!");
105             }
106 
107             // TODO: add child categories
108             Predicate[] categoryPredicates = idImplMap.stream().map(cat -> {
109                 Root<MCRCategoryLinkImpl> cl = criteriaQuery.from(MCRCategoryLinkImpl.class);
110                 Root<MCRCategoryImpl> c = criteriaQuery.from(MCRCategoryImpl.class);
111 
112                 Predicate linkToObject = criteriaBuilder.equal(
113                     cl.get(MCRCategoryLinkImpl_.objectReference).get(MCRCategLinkReference_.OBJECT_ID),
114                     oe.get(MCRObjectInfoEntity_.ID));
115 
116                 Predicate categoryToLink = criteriaBuilder.equal(cl.get(MCRCategoryLinkImpl_.CATEGORY),
117                     c.get(MCRCategoryImpl_.INTERNAL_ID));
118 
119                 Predicate between = criteriaBuilder.between(
120                     c.get(MCRCategoryImpl_.LEFT),
121                     cat.getLeft(),
122                     cat.getRight());
123 
124                 Predicate rootIdEqual = criteriaBuilder
125                     .equal(c.get(MCRCategoryImpl_.id).get(MCRCategoryID_.ROOT_ID), cat.getRootID());
126 
127                 return criteriaBuilder.and(linkToObject, categoryToLink, rootIdEqual, between);
128             }).toArray(Predicate[]::new);
129 
130             filters.add(criteriaBuilder.and(categoryPredicates));
131         }
132     }
133 
134     private List<MCRCategoryImpl> getCategories(MCRObjectQuery query, EntityManager em) {
135         CriteriaBuilder categCb = em.getCriteriaBuilder();
136         CriteriaQuery<MCRCategoryImpl> categQuery = categCb.createQuery(MCRCategoryImpl.class);
137         Root<MCRCategoryImpl> classRoot = categQuery.from(MCRCategoryImpl.class);
138         categQuery.select(classRoot);
139         List<MCRCategoryID> categoryIDList = query.getIncludeCategories().stream()
140             .map(MCRCategoryID::fromString)
141             .collect(Collectors.toList());
142 
143         categQuery.where(classRoot.get("id").in(categoryIDList));
144         TypedQuery<MCRCategoryImpl> typedQuery = em.createQuery(categQuery);
145 
146         return typedQuery.getResultList();
147     }
148 
149     protected void applySort(MCRObjectQuery query, CriteriaBuilder criteriaBuilder,
150         CriteriaQuery<MCRObjectInfoEntity> criteriaQuery, Root<MCRObjectInfoEntity> source) {
151         MCRObjectQuery.SortBy sf = query.sortBy() == null ? SORT_BY_DEFAULT : query.sortBy();
152 
153         SingularAttribute<MCRObjectInfoEntity, ?> attribute = switch (sf) {
154         case id -> MCRObjectInfoEntity_.id;
155         case created -> MCRObjectInfoEntity_.createDate;
156         case modified -> MCRObjectInfoEntity_.modifyDate;
157         };
158 
159         if (query.sortAsc() == null || query.sortAsc() == MCRObjectQuery.SortOrder.asc) {
160             criteriaQuery.orderBy(criteriaBuilder.asc(source.get(attribute)));
161         } else {
162             criteriaQuery.orderBy(criteriaBuilder.desc(source.get(attribute)));
163         }
164     }
165 
166     protected void applyLastId(MCRObjectQuery query, CriteriaBuilder criteriaBuilder,
167         CriteriaQuery<MCRObjectInfoEntity> criteriaQuery, Root<MCRObjectInfoEntity> source, List<Predicate> filters) {
168         if (query.sortBy() != MCRObjectQuery.SortBy.id && query.sortBy() != null) {
169             throw new UnsupportedOperationException("last id can not be used with " + query.sortBy());
170         }
171         if (query.sortAsc() == null || query.sortAsc() == MCRObjectQuery.SortOrder.asc) {
172             filters.add(criteriaBuilder.greaterThan(source.get(MCRObjectInfoEntity_.id), query.afterId()));
173             criteriaQuery.orderBy(criteriaBuilder.asc(source.get(MCRObjectInfoEntity_.id)));
174         } else {
175             filters.add(criteriaBuilder.lessThan(source.get(MCRObjectInfoEntity_.id), query.afterId()));
176             criteriaQuery.orderBy(criteriaBuilder.desc(source.get(MCRObjectInfoEntity_.id)));
177         }
178     }
179 
180     private List<Predicate> getFilter(MCRObjectQuery query, CriteriaBuilder criteriaBuilder,
181         Root<MCRObjectInfoEntity> source) {
182         List<Predicate> predicates = new ArrayList<>();
183 
184         Optional.ofNullable(query.type())
185             .map(type -> criteriaBuilder.equal(source.get(MCRObjectInfoEntity_.objectType), type))
186             .ifPresent(predicates::add);
187 
188         Optional.ofNullable(query.project())
189             .map(project -> criteriaBuilder.equal(source.get(MCRObjectInfoEntity_.objectProject), project))
190             .ifPresent(predicates::add);
191 
192         Optional.ofNullable(query.createdBy())
193             .map(creator -> criteriaBuilder.equal(source.get(MCRObjectInfoEntity_.createdBy), creator))
194             .ifPresent(predicates::add);
195 
196         Optional.ofNullable(query.modifiedBy())
197             .map(modifier -> criteriaBuilder.equal(source.get(MCRObjectInfoEntity_.modifiedBy), modifier))
198             .ifPresent(predicates::add);
199 
200         Optional.ofNullable(query.deletedBy())
201             .map(deleter -> criteriaBuilder.equal(source.get(MCRObjectInfoEntity_.deletedBy), deleter))
202             .ifPresent(predicates::add);
203 
204         Optional.ofNullable(query.createdAfter())
205             .map(date -> criteriaBuilder.greaterThanOrEqualTo(source.get(MCRObjectInfoEntity_.createDate), date))
206             .ifPresent(predicates::add);
207 
208         Optional.ofNullable(query.createdBefore())
209             .map(date -> criteriaBuilder.lessThanOrEqualTo(source.get(MCRObjectInfoEntity_.createDate), date))
210             .ifPresent(predicates::add);
211 
212         Optional.ofNullable(query.modifiedAfter())
213             .map(date -> criteriaBuilder.greaterThanOrEqualTo(source.get(MCRObjectInfoEntity_.modifyDate), date))
214             .ifPresent(predicates::add);
215 
216         Optional.ofNullable(query.modifiedBefore())
217             .map(date -> criteriaBuilder.lessThanOrEqualTo(source.get(MCRObjectInfoEntity_.modifyDate), date))
218             .ifPresent(predicates::add);
219 
220         Optional.ofNullable(query.deletedAfter())
221             .map(date -> criteriaBuilder.greaterThanOrEqualTo(source.get(MCRObjectInfoEntity_.deleteDate), date))
222             .ifPresent(predicates::add);
223 
224         Optional.ofNullable(query.deletedBefore())
225             .map(date -> criteriaBuilder.lessThanOrEqualTo(source.get(MCRObjectInfoEntity_.deleteDate), date))
226             .ifPresent(predicates::add);
227 
228         Optional.of(query.numberGreater())
229             .filter(numberBiggerThan -> numberBiggerThan > -1)
230             .map(numberBiggerThan -> criteriaBuilder.greaterThan(source.get(MCRObjectInfoEntity_.objectNumber),
231                 numberBiggerThan))
232             .ifPresent(predicates::add);
233 
234         Optional.of(query.numberLess())
235             .filter(numberLess -> numberLess > -1)
236             .map(numberLess -> criteriaBuilder.lessThan(source.get(MCRObjectInfoEntity_.objectNumber), numberLess))
237             .ifPresent(predicates::add);
238 
239         Optional.ofNullable(query.status())
240             .map(state -> criteriaBuilder.equal(source.get(MCRObjectInfoEntity_.state), state))
241             .ifPresent(predicates::add);
242         /*
243          per default, we only query not deleted objects, but if the use queries one of the deleted fields
244          then deleted objects are included
245          */
246         if (Optional.ofNullable(query.deletedBy()).isEmpty() &&
247             Optional.ofNullable(query.deletedBefore()).isEmpty() &&
248             Optional.ofNullable(query.deletedAfter()).isEmpty()) {
249             predicates.add(criteriaBuilder.isNull(source.get(MCRObjectInfoEntity_.deleteDate)));
250             predicates.add(criteriaBuilder.isNull(source.get(MCRObjectInfoEntity_.deletedBy)));
251         }
252 
253         return predicates;
254     }
255 
256     @Override
257     public List<MCRObjectID> getIds(MCRObjectQuery objectQuery) {
258         TypedQuery<MCRObjectInfoEntity> typedQuery = convertQuery(objectQuery);
259         return typedQuery.getResultList()
260             .stream()
261             .map(MCRObjectInfoEntity::getId)
262             .collect(Collectors.toList());
263     }
264 
265     @Override
266     public List<MCRObjectIDDate> getIdDates(MCRObjectQuery objectQuery) {
267         TypedQuery<MCRObjectInfoEntity> typedQuery = convertQuery(objectQuery);
268         return typedQuery.getResultList()
269             .stream()
270             .map(entity -> new MCRObjectIDDateImpl(Date.from(entity.getModifyDate()), entity.getId().toString()))
271             .collect(Collectors.toList());
272     }
273 
274     @Override
275     public List<MCRObjectInfo> getInfos(MCRObjectQuery objectQuery) {
276         TypedQuery<MCRObjectInfoEntity> typedQuery = convertQuery(objectQuery);
277         EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
278 
279         return typedQuery.getResultList()
280             .stream()
281             .peek(em::detach)
282             .collect(Collectors.toList());
283     }
284 
285     @Override
286     public int count(MCRObjectQuery objectQuery) {
287         EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
288         CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
289         CriteriaQuery<Number> criteriaQuery = criteriaBuilder.createQuery(Number.class);
290         Root<MCRObjectInfoEntity> source = criteriaQuery.from(MCRObjectInfoEntity.class);
291         criteriaQuery.select(criteriaBuilder.count(source));
292 
293         List<Predicate> filters = getFilter(objectQuery, criteriaBuilder, source);
294         applyClassificationFilter(objectQuery, criteriaBuilder, criteriaQuery, source, filters, em);
295 
296         if (filters.size() > 0) {
297             criteriaQuery.where(criteriaBuilder.and(filters.toArray(new Predicate[0])));
298         }
299 
300         return em.createQuery(criteriaQuery).getSingleResult().intValue();
301     }
302 }