Project

General

Profile

Download (15.7 KB) Statistics
| Branch: | Tag: | Revision:
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.common.DefaultProgressMonitor;
29
import eu.etaxonomy.cdm.common.IProgressMonitor;
30
import eu.etaxonomy.cdm.model.common.CdmBase;
31
import eu.etaxonomy.cdm.model.common.ISourceable;
32
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
33
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
34
import eu.etaxonomy.cdm.model.common.LSID;
35
import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
36
import eu.etaxonomy.cdm.model.media.Rights;
37
import eu.etaxonomy.cdm.model.reference.Reference;
38
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
39
import eu.etaxonomy.cdm.persistence.dao.common.IIdentifiableDao;
40
import eu.etaxonomy.cdm.persistence.query.MatchMode;
41
import eu.etaxonomy.cdm.persistence.query.OrderHint;
42
import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
43
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
44
import eu.etaxonomy.cdm.strategy.match.DefaultMatchStrategy;
45
import eu.etaxonomy.cdm.strategy.match.IMatchStrategy;
46
import eu.etaxonomy.cdm.strategy.match.IMatchable;
47
import eu.etaxonomy.cdm.strategy.match.MatchException;
48
import eu.etaxonomy.cdm.strategy.merge.IMergable;
49
import eu.etaxonomy.cdm.strategy.merge.IMergeStrategy;
50
import eu.etaxonomy.cdm.strategy.merge.MergeException;
51

    
52
public abstract class IdentifiableServiceBase<T extends IdentifiableEntity,DAO extends IIdentifiableDao<T>> extends AnnotatableServiceBase<T,DAO> 
53
						implements IIdentifiableEntityService<T>{
54
	
55
    @Autowired
56
    protected ICommonService commonService;
57

    
58
	
59
	protected static final int UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE = 1000;
60
	protected static final  Logger logger = Logger.getLogger(IdentifiableServiceBase.class);
61

    
62
	@Transactional(readOnly = true)
63
	public Pager<Rights> getRights(T t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
64
        Integer numberOfResults = dao.countRights(t);
65
		
66
		List<Rights> results = new ArrayList<Rights>();
67
		if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
68
			results = dao.getRights(t, pageSize, pageNumber,propertyPaths); 
69
		}
70
		
71
		return new DefaultPagerImpl<Rights>(pageNumber, numberOfResults, pageSize, results);
72
	}
73
	
74
	@Transactional(readOnly = true)
75
	public Pager<IdentifiableSource> getSources(T t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
76
		 Integer numberOfResults = dao.countSources(t);
77
			
78
			List<IdentifiableSource> results = new ArrayList<IdentifiableSource>();
79
			if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
80
				results = dao.getSources(t, pageSize, pageNumber,propertyPaths); 
81
			}
82
			
83
			return new DefaultPagerImpl<IdentifiableSource>(pageNumber, numberOfResults, pageSize, results);
84
	}
85

    
86
	
87
	@Transactional(readOnly = false)
88
	public T replace(T x, T y) {
89
		return dao.replace(x, y);
90
	}
91
	/**
92
	 * FIXME Candidate for harmonization
93
	 * Given that this method is strongly typed, and generic, could we not simply expose it as
94
	 * List<T> findByTitle(String title) as it is somewhat less cumbersome. Admittedly, I don't 
95
	 * understand what is going on with the configurators etc. so maybe there is a good reason for
96
	 * the design of this method. 
97
	 * @param title
98
	 * @return
99
	 */
100
	@Transactional(readOnly = true)
101
	protected List<T> findCdmObjectsByTitle(String title){
102
		return ((IIdentifiableDao)dao).findByTitle(title);
103
	}
104
	
105
	@Transactional(readOnly = true)
106
	protected List<T> findCdmObjectsByTitle(String title, Class<T> clazz){
107
		return ((IIdentifiableDao)dao).findByTitleAndClass(title, clazz);
108
	}
109
	@Transactional(readOnly = true)
110
	protected List<T> findCdmObjectsByTitle(String title, CdmBase sessionObject){
111
		return ((IIdentifiableDao)dao).findByTitle(title, sessionObject);
112
	}
113
	
114
	/*
115
	 * TODO - Migrated from CommonServiceBase
116
	 *  (non-Javadoc)
117
	 * @see eu.etaxonomy.cdm.api.service.ICommonService#getSourcedObjectById(java.lang.String, java.lang.String)
118
	 */
119
	@Transactional(readOnly = true)
120
	public ISourceable getSourcedObjectByIdInSource(Class clazz, String idInSource, String idNamespace) {
121
		ISourceable result = null;
122

    
123
		List<T> list = dao.findOriginalSourceByIdInSource(idInSource, idNamespace);
124
		if (! list.isEmpty()){
125
			result = list.get(0);
126
		}
127
		return result;
128
	}
129
	
130
	/* (non-Javadoc)
131
	 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#getUuidAndTitleCache()
132
	 */
133
	@Transactional(readOnly = true)
134
	public List<UuidAndTitleCache<T>> getUuidAndTitleCache() {
135
		return dao.getUuidAndTitleCache();
136
	}
137
	
138
	@Transactional(readOnly = true)
139
	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) {
140
		 Integer numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
141
			
142
		 List<T> results = new ArrayList<T>();
143
		 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
144
				results = dao.findByTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths); 
145
		 }
146
			
147
		  return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
148
	}
149
	
150
	@Transactional(readOnly = true)
151
	public Pager<T> findByTitle(IIdentifiableEntityServiceConfigurator<T> config){
152
		return findByTitle(config.getClazz(), config.getTitleSearchStringSqlized(), config.getMatchMode(), config.getCriteria(), config.getPageSize(), config.getPageNumber(), config.getOrderHints(), config.getPropertyPaths());
153
	}
154
	
155
	@Transactional(readOnly = true)
156
	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) {
157
		 Integer numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
158
			
159
		 List<T> results = new ArrayList<T>();
160
		 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
161
				results = dao.findByTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths); 
162
		 }
163
		 return results;
164
	}
165

    
166
	@Transactional(readOnly = true)
167
	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) {
168
		 Integer numberOfResults = dao.countByReferenceTitle(clazz, queryString, matchmode, criteria);
169
			
170
		 List<T> results = new ArrayList<T>();
171
		 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
172
				results = dao.findByReferenceTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths); 
173
		 }
174
		 return results;
175
	}
176
	
177
	@Transactional(readOnly = true)
178
	public T find(LSID lsid) {
179
		return dao.find(lsid);
180
	}
181
	
182
	@Transactional(readOnly = true)
183
	public Pager<T> search(Class<? extends T> clazz, String queryString, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
184
        Integer numberOfResults = dao.count(clazz,queryString);
185
		
186
		List<T> results = new ArrayList<T>();
187
		if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
188
			results = dao.search(clazz,queryString, pageSize, pageNumber, orderHints, propertyPaths); 
189
		}
190
		
191
		return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
192
	}
193
	
194

    
195
	/* (non-Javadoc)
196
	 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache()
197
	 */
198
	@Override
199
	public void updateTitleCache() {
200
		updateTitleCache(null, null, null, null);
201
	}
202
	
203
	@Transactional(readOnly = false)  //TODO check transactional behaviour, e.g. what happens with the session if count is very large 
204
	protected void updateTitleCacheImpl(Class<? extends T> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<T> cacheStrategy, IProgressMonitor monitor) {
205
		if (stepSize == null){
206
			stepSize = UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE;
207
		}
208
		if (monitor == null){
209
			monitor = DefaultProgressMonitor.NewInstance();
210
		}
211
		
212
		int count = dao.count(clazz);
213
		monitor.beginTask("update titles", count);
214
		int worked = 0;
215
		for(int i = 0 ; i < count ; i = i + stepSize){
216
			// not sure if such strict ordering is necessary here, but for safety reasons I do it
217
			ArrayList<OrderHint> orderHints = new ArrayList<OrderHint>();
218
			orderHints.add( new OrderHint("id", OrderHint.SortOrder.ASCENDING));
219
			List<T> list = this.list(clazz, stepSize, i, orderHints, null);
220
			List<T> entitiesToUpdate = new ArrayList<T>();
221
			for (T entity : list){
222
				if (entity.isProtectedTitleCache() == false){
223
					IIdentifiableEntityCacheStrategy entityCacheStrategy = cacheStrategy;
224
					if (entityCacheStrategy == null){
225
						entityCacheStrategy = entity.getCacheStrategy();
226
						//FIXME find out why the wrong cache strategy is loaded here, see #1876 
227
						if (entity instanceof Reference){
228
							entityCacheStrategy = ReferenceFactory.newReference(((Reference)entity).getType()).getCacheStrategy();
229
						}
230
					}
231
					entity.setCacheStrategy(entityCacheStrategy);
232
					entity.setProtectedTitleCache(true);
233
					String titleCache = entity.getTitleCache();
234
					entity.setProtectedTitleCache(false);
235
					setOtherCachesNull(entity); //TODO find better solution
236
					String newTitleCache = entityCacheStrategy.getTitleCache(entity);
237
					if (titleCache == null || titleCache != null && ! titleCache.equals(newTitleCache)){
238
						entity.setTitleCache(null, false);
239
						entity.getTitleCache();
240
						entitiesToUpdate.add(entity);
241
					}
242
				}
243
			}
244
			saveOrUpdate(entitiesToUpdate);
245
			monitor.worked(worked++);
246
			if (monitor.isCanceled()){
247
				monitor.done();
248
				return;
249
			}
250
		}
251
		monitor.done();
252
	}
253
	
254
	
255

    
256
	/**
257
	 * Needs override if not only the title cache should be set to null to
258
	 * generate the correct new title cache
259
	 */
260
	protected void setOtherCachesNull(T entity) {
261
		return;
262
	}
263
	
264

    
265
	
266
	private class DeduplicateState{
267
		String lastTitleCache;
268
		Integer pageSize = 50;
269
		int nPages = 3;
270
		int startPage = 0;
271
		boolean isCompleted = false;
272
		int result; 
273
	}
274
	
275
	/* (non-Javadoc)
276
	 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#deduplicate(java.lang.Class, eu.etaxonomy.cdm.strategy.match.IMatchStrategy, eu.etaxonomy.cdm.strategy.merge.IMergeStrategy)
277
	 */
278
	@Override
279
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = false)
280
	public int deduplicate(Class<? extends T> clazz, IMatchStrategy matchStrategy, IMergeStrategy mergeStrategy) {
281
		DeduplicateState dedupState = new DeduplicateState();
282
		
283
		if (clazz == null){
284
			logger.warn("Deduplication clazz must not be null!");
285
			return 0;
286
		}
287
		if (! ( IMatchable.class.isAssignableFrom(clazz) && IMergable.class.isAssignableFrom(clazz) )  ){
288
			logger.warn("Deduplication implemented only for classes implementing IMatchable and IMergeable. No deduplication performed!");
289
			return 0;
290
		}
291
		Class matchableClass = clazz;
292
		if (matchStrategy == null){
293
			matchStrategy = DefaultMatchStrategy.NewInstance(matchableClass);
294
		}
295
		List<T> nextGroup = new ArrayList<T>();
296
		
297
		int result = 0;
298
//		double countTotal = count(clazz);
299
//		
300
//		Number countPagesN = Math.ceil(countTotal/dedupState.pageSize.doubleValue()) ; 
301
//		int countPages = countPagesN.intValue();
302
//		
303
		
304
		List<OrderHint> orderHints = Arrays.asList(new OrderHint[]{new OrderHint("titleCache", SortOrder.ASCENDING)});
305
		
306
		while (! dedupState.isCompleted){
307
			//get x page sizes
308
			List<T> objectList = getPages(clazz, dedupState, orderHints);
309
			//after each page check if any changes took place
310
			int nUnEqualPages = handleAllPages(objectList, dedupState, nextGroup, matchStrategy, mergeStrategy);
311
			nUnEqualPages = nUnEqualPages + dedupState.pageSize * dedupState.startPage;
312
			//refresh start page counter
313
			int finishedPages = nUnEqualPages / dedupState.pageSize;
314
			dedupState.startPage = finishedPages;
315
		}
316
				
317
		result += handleLastGroup(nextGroup, matchStrategy, mergeStrategy);
318
		return result;
319
	}
320

    
321

    
322
	private int handleAllPages(List<T> objectList, DeduplicateState dedupState, List<T> nextGroup, IMatchStrategy matchStrategy, IMergeStrategy mergeStrategy) {
323
		int nUnEqual = 0;
324
		for (T object : objectList){
325
			String currentTitleCache = object.getTitleCache();
326
			if (currentTitleCache != null && currentTitleCache.equals(dedupState.lastTitleCache)){
327
				//=titleCache
328
				nextGroup.add(object);
329
			}else{
330
				//<> titleCache
331
				dedupState.result += handleLastGroup(nextGroup, matchStrategy, mergeStrategy);
332
				nextGroup = new ArrayList<T>();
333
				nextGroup.add(object);
334
				nUnEqual++;	
335
			}
336
			dedupState.lastTitleCache = currentTitleCache;
337
		}
338
		handleLastGroup(nextGroup, matchStrategy, mergeStrategy);
339
		return nUnEqual;
340
	}
341

    
342
	private List<T> getPages(Class<? extends T> clazz, DeduplicateState dedupState, List<OrderHint> orderHints) {
343
		List<T> result = new ArrayList<T>();
344
		for (int pageNo = dedupState.startPage; pageNo < dedupState.startPage + dedupState.nPages; pageNo++){
345
			List<T> objectList = listByTitle(clazz, null, null, null, dedupState.pageSize, pageNo, orderHints, null);
346
			result.addAll(objectList);
347
		}
348
		if (result.size()< dedupState.nPages * dedupState.pageSize ){
349
			dedupState.isCompleted = true;
350
		}
351
		return result;
352
	}
353

    
354
	private int handleLastGroup(List<T> group, IMatchStrategy matchStrategy, IMergeStrategy mergeStrategy) {
355
		int result = 0;
356
		int size = group.size();
357
		Set<Integer> exclude = new HashSet<Integer>();  //set to collect all objects, that have been merged already
358
		for (int i = 0; i < size - 1; i++){
359
			if (exclude.contains(i)){
360
				continue;
361
			}
362
			for (int j = i + 1; j < size; j++){
363
				if (exclude.contains(j)){
364
					continue;
365
				}
366
				T firstObject = group.get(i);
367
				T secondObject = group.get(j);
368
				
369
				try {
370
					if (matchStrategy.invoke((IMatchable)firstObject, (IMatchable)secondObject)){
371
						commonService.merge((IMergable)firstObject, (IMergable)secondObject, mergeStrategy);
372
						exclude.add(j);
373
						result++;
374
					}
375
				} catch (MatchException e) {
376
					logger.warn("MatchException when trying to match " + firstObject.getTitleCache());
377
					e.printStackTrace();
378
				} catch (MergeException e) {
379
					logger.warn("MergeException when trying to merge " + firstObject.getTitleCache());
380
					e.printStackTrace();
381
				}
382
			}
383
		}
384
		return result;
385
	}	
386
	
387
	 public Integer countByTitle(Class<? extends T> clazz, String queryString,MatchMode matchmode, List<Criterion> criteria){
388
		 Integer numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
389
		 
390
		 return numberOfResults;
391
	 }
392
	 
393
	@Transactional(readOnly = true)
394
	public Integer countByTitle(IIdentifiableEntityServiceConfigurator<T> config){
395
		return countByTitle(config.getClazz(), config.getTitleSearchStringSqlized(),
396
				config.getMatchMode(), config.getCriteria());
397
		
398
	}
399

    
400
}
401

    
(53-53/76)