c7dfdced1e06d40728bcc0d7c41a3a9798dfa687
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / IdentifiableServiceBase.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.api.service;
12
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Set;
18
19 import org.apache.log4j.Logger;
20 import org.hibernate.criterion.Criterion;
21 import org.springframework.beans.factory.annotation.Autowired;
22 import org.springframework.transaction.annotation.Propagation;
23 import org.springframework.transaction.annotation.Transactional;
24
25 import eu.etaxonomy.cdm.api.service.config.IIdentifiableEntityServiceConfigurator;
26 import eu.etaxonomy.cdm.api.service.pager.Pager;
27 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
28 import eu.etaxonomy.cdm.model.common.CdmBase;
29 import eu.etaxonomy.cdm.model.common.ISourceable;
30 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
31 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
32 import eu.etaxonomy.cdm.model.common.LSID;
33 import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
34 import eu.etaxonomy.cdm.model.media.Rights;
35 import eu.etaxonomy.cdm.model.reference.Reference;
36 import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
37 import eu.etaxonomy.cdm.persistence.dao.common.IIdentifiableDao;
38 import eu.etaxonomy.cdm.persistence.query.MatchMode;
39 import eu.etaxonomy.cdm.persistence.query.OrderHint;
40 import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
41 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
42 import eu.etaxonomy.cdm.strategy.match.DefaultMatchStrategy;
43 import eu.etaxonomy.cdm.strategy.match.IMatchStrategy;
44 import eu.etaxonomy.cdm.strategy.match.IMatchable;
45 import eu.etaxonomy.cdm.strategy.match.MatchException;
46 import eu.etaxonomy.cdm.strategy.merge.IMergable;
47 import eu.etaxonomy.cdm.strategy.merge.IMergeStrategy;
48 import eu.etaxonomy.cdm.strategy.merge.MergeException;
49
50 public abstract class IdentifiableServiceBase<T extends IdentifiableEntity,DAO extends IIdentifiableDao<T>> extends AnnotatableServiceBase<T,DAO>
51 implements IIdentifiableEntityService<T>{
52
53 @Autowired
54 protected ICommonService commonService;
55
56
57 protected static final int UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE = 1000;
58 protected static final Logger logger = Logger.getLogger(IdentifiableServiceBase.class);
59
60 @Transactional(readOnly = true)
61 public Pager<Rights> getRights(T t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
62 Integer numberOfResults = dao.countRights(t);
63
64 List<Rights> results = new ArrayList<Rights>();
65 if(numberOfResults > 0) { // no point checking again
66 results = dao.getRights(t, pageSize, pageNumber,propertyPaths);
67 }
68
69 return new DefaultPagerImpl<Rights>(pageNumber, numberOfResults, pageSize, results);
70 }
71
72 @Transactional(readOnly = true)
73 public Pager<IdentifiableSource> getSources(T t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
74 Integer numberOfResults = dao.countSources(t);
75
76 List<IdentifiableSource> results = new ArrayList<IdentifiableSource>();
77 if(numberOfResults > 0) { // no point checking again
78 results = dao.getSources(t, pageSize, pageNumber,propertyPaths);
79 }
80
81 return new DefaultPagerImpl<IdentifiableSource>(pageNumber, numberOfResults, pageSize, results);
82 }
83
84 @Transactional(readOnly = true)
85 protected List<T> findByTitle(IIdentifiableEntityServiceConfigurator config){
86 return ((IIdentifiableDao)dao).findByTitle(config.getTitleSearchString(),
87 config.getMatchMode(), 0, -1, null);
88 // TODO: Implement parameters pageSize, pageNumber, and criteria
89 }
90
91 @Transactional(readOnly = false)
92 public T replace(T x, T y) {
93 return dao.replace(x, y);
94 }
95 /**
96 * FIXME Candidate for harmonization
97 * Given that this method is strongly typed, and generic, could we not simply expose it as
98 * List<T> findByTitle(String title) as it is somewhat less cumbersome. Admittedly, I don't
99 * understand what is going on with the configurators etc. so maybe there is a good reason for
100 * the design of this method.
101 * @param title
102 * @return
103 */
104 @Transactional(readOnly = true)
105 protected List<T> findCdmObjectsByTitle(String title){
106 return ((IIdentifiableDao)dao).findByTitle(title);
107 }
108
109 @Transactional(readOnly = true)
110 protected List<T> findCdmObjectsByTitle(String title, Class<T> clazz){
111 return ((IIdentifiableDao)dao).findByTitleAndClass(title, clazz);
112 }
113 @Transactional(readOnly = true)
114 protected List<T> findCdmObjectsByTitle(String title, CdmBase sessionObject){
115 return ((IIdentifiableDao)dao).findByTitle(title, sessionObject);
116 }
117
118 /*
119 * TODO - Migrated from CommonServiceBase
120 * (non-Javadoc)
121 * @see eu.etaxonomy.cdm.api.service.ICommonService#getSourcedObjectById(java.lang.String, java.lang.String)
122 */
123 @Transactional(readOnly = true)
124 public ISourceable getSourcedObjectByIdInSource(Class clazz, String idInSource, String idNamespace) {
125 ISourceable result = null;
126
127 List<T> list = dao.findOriginalSourceByIdInSource(idInSource, idNamespace);
128 if (! list.isEmpty()){
129 result = list.get(0);
130 }
131 return result;
132 }
133
134 /* (non-Javadoc)
135 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#getUuidAndTitleCache()
136 */
137 @Transactional(readOnly = true)
138 public List<UuidAndTitleCache<T>> getUuidAndTitleCache() {
139 return dao.getUuidAndTitleCache();
140 }
141
142 @Transactional(readOnly = true)
143 public Pager<T> findByTitle(Class<? extends T> clazz, String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
144 Integer numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
145
146 List<T> results = new ArrayList<T>();
147 if(numberOfResults > 0) { // no point checking again
148 results = dao.findByTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
149 }
150
151 return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
152 }
153
154 @Transactional(readOnly = true)
155 public List<T> listByTitle(Class<? extends T> clazz, String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
156 Integer numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
157
158 List<T> results = new ArrayList<T>();
159 if(numberOfResults > 0) { // no point checking again
160 results = dao.findByTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
161 }
162 return results;
163 }
164
165 @Transactional(readOnly = true)
166 public List<T> listByReferenceTitle(Class<? extends T> clazz, String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
167 Integer numberOfResults = dao.countByReferenceTitle(clazz, queryString, matchmode, criteria);
168
169 List<T> results = new ArrayList<T>();
170 if(numberOfResults > 0) { // no point checking again
171 results = dao.findByReferenceTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
172 }
173 return results;
174 }
175
176 @Transactional(readOnly = true)
177 public T find(LSID lsid) {
178 return dao.find(lsid);
179 }
180
181 @Transactional(readOnly = true)
182 public Pager<T> search(Class<? extends T> clazz, String queryString, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
183 Integer numberOfResults = dao.count(clazz,queryString);
184
185 List<T> results = new ArrayList<T>();
186 if(numberOfResults > 0) { // no point checking again
187 results = dao.search(clazz,queryString, pageSize, pageNumber, orderHints, propertyPaths);
188 }
189
190 return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
191 }
192
193 @Transactional(readOnly = false)
194 public void updateTitleCache(Class<? extends T> clazz) {
195 IIdentifiableEntityCacheStrategy<T> cacheStrategy = null;
196 updateTitleCache(clazz, UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE, cacheStrategy);
197 }
198
199
200 @Transactional(readOnly = false) //TODO check transactional behaviour, e.g. what happens with the session if count is very large
201 public void updateTitleCache(Class<? extends T> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<T> cacheStrategy) {
202 if (stepSize == null){
203 stepSize = UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE;
204 }
205
206 int count = dao.count(clazz);
207 for(int i = 0 ; i < count ; i = i + stepSize){
208 // not sure if such strict ordering is necessary here, but for safety reasons I do it
209 ArrayList<OrderHint> orderHints = new ArrayList<OrderHint>();
210 orderHints.add( new OrderHint("id", OrderHint.SortOrder.ASCENDING));
211 List<T> list = this.list(clazz, stepSize, i, orderHints, null);
212 List<T> entitiesToUpdate = new ArrayList<T>();
213 for (T entity : list){
214 if (entity.isProtectedTitleCache() == false){
215 IIdentifiableEntityCacheStrategy entityCacheStrategy = cacheStrategy;
216 if (entityCacheStrategy == null){
217 entityCacheStrategy = entity.getCacheStrategy();
218 //FIXME find out why the wrong cache strategy is loaded here, see #1876
219 if (entity instanceof Reference){
220 entityCacheStrategy = ReferenceFactory.newReference(((Reference)entity).getType()).getCacheStrategy();
221 }
222 }
223 entity.setCacheStrategy(entityCacheStrategy);
224 //TODO this won't work for those classes that always generate the title cache new
225 String titleCache = entity.getTitleCache();
226 setOtherCachesNull(entity); //TODO find better solution
227 String newTitleCache = entityCacheStrategy.getTitleCache(entity);
228 if (titleCache == null || titleCache != null && ! titleCache.equals(newTitleCache)){
229 entity.setTitleCache(null, false);
230 entity.getTitleCache();
231 entitiesToUpdate.add(entity);
232 }
233 }
234 }
235 saveOrUpdate(entitiesToUpdate);
236
237 }
238 }
239
240
241
242 /**
243 * Needs override if not only the title cache should be set to null to
244 * generate the correct new title cache
245 */
246 protected void setOtherCachesNull(T entity) {
247 return;
248 }
249
250
251 /* (non-Javadoc)
252 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#deduplicate(java.lang.Class, eu.etaxonomy.cdm.strategy.match.IMatchStrategy, eu.etaxonomy.cdm.strategy.merge.IMergeStrategy)
253 */
254 @Override
255 @Transactional(propagation = Propagation.SUPPORTS, readOnly = false)
256 public int deduplicate(Class<? extends T> clazz, IMatchStrategy matchStrategy, IMergeStrategy mergeStrategy) {
257 if (clazz == null){
258 logger.warn("Deduplication clazz must not be null!");
259 return 0;
260 }
261 if (! ( IMatchable.class.isAssignableFrom(clazz) && IMergable.class.isAssignableFrom(clazz) ) ){
262 logger.warn("Deduplication implemented only for classes implementing IMatchable and IMergeable. No deduplication performed!");
263 return 0;
264 }
265 Class matchableClass = clazz;
266 if (matchStrategy == null){
267 matchStrategy = DefaultMatchStrategy.NewInstance(matchableClass);
268 }
269
270 int result = 0;
271 double countTotal = count(clazz);
272 Integer pageSize = 1000;
273 List<T> nextGroup = new ArrayList<T>();
274 String lastTitleCache = null;
275
276 Number countPagesN = Math.ceil(countTotal/pageSize.doubleValue()) ;
277 int countPages = countPagesN.intValue();
278
279 //TODO test paging
280 for (int i = 0; i< countPages ; i++){
281 List<OrderHint> orderHints = Arrays.asList(new OrderHint[]{new OrderHint("titleCache", SortOrder.ASCENDING)});
282 List<T> objectList = listByTitle(clazz, null, null, null, pageSize, i, orderHints, null);
283
284 for (T object : objectList){
285 String currentTitleCache = object.getTitleCache();
286 if (currentTitleCache != null && currentTitleCache.equals(lastTitleCache)){
287 //=titleCache
288 nextGroup.add(object);
289 }else{
290 //<> titleCache
291 result += handleLastGroup(nextGroup, matchStrategy, mergeStrategy);
292 nextGroup = new ArrayList<T>();
293 nextGroup.add(object);
294 }
295 lastTitleCache = currentTitleCache;
296 }
297 }
298 result += handleLastGroup(nextGroup, matchStrategy, mergeStrategy);
299 return result;
300 }
301
302 private int handleLastGroup(List<T> group, IMatchStrategy matchStrategy, IMergeStrategy mergeStrategy) {
303 int result = 0;
304 int size = group.size();
305 Set<Integer> exclude = new HashSet<Integer>(); //set to collect all objects, that have been merged already
306 for (int i = 0; i < size - 1; i++){
307 if (exclude.contains(i)){
308 continue;
309 }
310 for (int j = i + 1; j < size; j++){
311 if (exclude.contains(j)){
312 continue;
313 }
314 T firstObject = group.get(i);
315 T secondObject = group.get(j);
316
317 try {
318 if (matchStrategy.invoke((IMatchable)firstObject, (IMatchable)secondObject)){
319 commonService.merge((IMergable)firstObject, (IMergable)secondObject, mergeStrategy);
320 exclude.add(j);
321 result++;
322 }
323 } catch (MatchException e) {
324 logger.warn("MatchException when trying to match " + firstObject.getTitleCache());
325 e.printStackTrace();
326 } catch (MergeException e) {
327 logger.warn("MergeException when trying to merge " + firstObject.getTitleCache());
328 e.printStackTrace();
329 }
330 }
331 }
332 return result;
333 }
334
335 }
336