3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
11 package eu
.etaxonomy
.cdm
.api
.service
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Arrays
;
15 import java
.util
.HashSet
;
16 import java
.util
.List
;
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
;
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
.monitor
.DefaultProgressMonitor
;
29 import eu
.etaxonomy
.cdm
.common
.monitor
.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
.name
.NonViralName
;
38 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
39 import eu
.etaxonomy
.cdm
.model
.reference
.ReferenceFactory
;
40 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IIdentifiableDao
;
41 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
42 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
43 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
44 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
45 import eu
.etaxonomy
.cdm
.strategy
.match
.DefaultMatchStrategy
;
46 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchStrategy
;
47 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchable
;
48 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchException
;
49 import eu
.etaxonomy
.cdm
.strategy
.merge
.IMergable
;
50 import eu
.etaxonomy
.cdm
.strategy
.merge
.IMergeStrategy
;
51 import eu
.etaxonomy
.cdm
.strategy
.merge
.MergeException
;
53 public abstract class IdentifiableServiceBase
<T
extends IdentifiableEntity
,DAO
extends IIdentifiableDao
<T
>> extends AnnotatableServiceBase
<T
,DAO
>
54 implements IIdentifiableEntityService
<T
>{
57 protected ICommonService commonService
;
60 protected static final int UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE
= 1000;
61 protected static final Logger logger
= Logger
.getLogger(IdentifiableServiceBase
.class);
63 @Transactional(readOnly
= true)
64 public Pager
<Rights
> getRights(T t
, Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
65 Integer numberOfResults
= dao
.countRights(t
);
67 List
<Rights
> results
= new ArrayList
<Rights
>();
68 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
69 results
= dao
.getRights(t
, pageSize
, pageNumber
,propertyPaths
);
72 return new DefaultPagerImpl
<Rights
>(pageNumber
, numberOfResults
, pageSize
, results
);
75 @Transactional(readOnly
= true)
76 public Pager
<IdentifiableSource
> getSources(T t
, Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
77 Integer numberOfResults
= dao
.countSources(t
);
79 List
<IdentifiableSource
> results
= new ArrayList
<IdentifiableSource
>();
80 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
81 results
= dao
.getSources(t
, pageSize
, pageNumber
,propertyPaths
);
84 return new DefaultPagerImpl
<IdentifiableSource
>(pageNumber
, numberOfResults
, pageSize
, results
);
88 @Transactional(readOnly
= false)
89 public T
replace(T x
, T y
) {
90 return dao
.replace(x
, y
);
93 * FIXME Candidate for harmonization
94 * Given that this method is strongly typed, and generic, could we not simply expose it as
95 * List<T> findByTitle(String title) as it is somewhat less cumbersome. Admittedly, I don't
96 * understand what is going on with the configurators etc. so maybe there is a good reason for
97 * the design of this method.
101 @Transactional(readOnly
= true)
102 protected List
<T
> findCdmObjectsByTitle(String title
){
103 return ((IIdentifiableDao
)dao
).findByTitle(title
);
106 @Transactional(readOnly
= true)
107 protected List
<T
> findCdmObjectsByTitle(String title
, Class
<T
> clazz
){
108 return ((IIdentifiableDao
)dao
).findByTitleAndClass(title
, clazz
);
110 @Transactional(readOnly
= true)
111 protected List
<T
> findCdmObjectsByTitle(String title
, CdmBase sessionObject
){
112 return ((IIdentifiableDao
)dao
).findByTitle(title
, sessionObject
);
116 * TODO - Migrated from CommonServiceBase
118 * @see eu.etaxonomy.cdm.api.service.ICommonService#getSourcedObjectById(java.lang.String, java.lang.String)
120 @Transactional(readOnly
= true)
121 public ISourceable
getSourcedObjectByIdInSource(Class clazz
, String idInSource
, String idNamespace
) {
122 ISourceable result
= null;
124 List
<T
> list
= dao
.findOriginalSourceByIdInSource(idInSource
, idNamespace
);
125 if (! list
.isEmpty()){
126 result
= list
.get(0);
132 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#getUuidAndTitleCache()
134 @Transactional(readOnly
= true)
135 public List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache() {
136 return dao
.getUuidAndTitleCache();
139 @Transactional(readOnly
= true)
140 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
) {
141 Integer numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
143 List
<T
> results
= new ArrayList
<T
>();
144 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
145 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
148 return new DefaultPagerImpl
<T
>(pageNumber
, numberOfResults
, pageSize
, results
);
151 @Transactional(readOnly
= true)
152 public Pager
<T
> findByTitle(IIdentifiableEntityServiceConfigurator
<T
> config
){
153 return findByTitle(config
.getClazz(), config
.getTitleSearchStringSqlized(), config
.getMatchMode(), config
.getCriteria(), config
.getPageSize(), config
.getPageNumber(), config
.getOrderHints(), config
.getPropertyPaths());
156 @Transactional(readOnly
= true)
157 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
) {
158 Integer numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
160 List
<T
> results
= new ArrayList
<T
>();
161 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
162 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
167 @Transactional(readOnly
= true)
168 public Pager
<T
> findTitleCache(Class
<?
extends T
> clazz
, String queryString
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, MatchMode matchMode
){
169 long numberOfResults
= dao
.countTitleCache(clazz
, queryString
, matchMode
);
171 List
<T
> results
= new ArrayList
<T
>();
172 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
173 results
= dao
.findTitleCache(clazz
, queryString
, pageSize
, pageNumber
, orderHints
, matchMode
);
176 r
+= numberOfResults
;
178 return new DefaultPagerImpl
<T
>(pageNumber
, r
, pageSize
, results
);
181 @Transactional(readOnly
= true)
182 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
) {
183 Integer numberOfResults
= dao
.countByReferenceTitle(clazz
, queryString
, matchmode
, criteria
);
185 List
<T
> results
= new ArrayList
<T
>();
186 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
187 results
= dao
.findByReferenceTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
192 @Transactional(readOnly
= true)
193 public T
find(LSID lsid
) {
194 return dao
.find(lsid
);
197 @Transactional(readOnly
= true)
198 public Pager
<T
> search(Class
<?
extends T
> clazz
, String queryString
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
199 Integer numberOfResults
= dao
.count(clazz
,queryString
);
201 List
<T
> results
= new ArrayList
<T
>();
202 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
203 results
= dao
.search(clazz
,queryString
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
206 return new DefaultPagerImpl
<T
>(pageNumber
, numberOfResults
, pageSize
, results
);
211 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache()
214 @Transactional(readOnly
= false)
215 public void updateTitleCache() {
216 updateTitleCache(null, null, null, null);
219 @Transactional(readOnly
= false) //TODO check transactional behaviour, e.g. what happens with the session if count is very large
220 protected void updateTitleCacheImpl(Class
<?
extends T
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<T
> cacheStrategy
, IProgressMonitor monitor
) {
221 if (stepSize
== null){
222 stepSize
= UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE
;
224 if (monitor
== null){
225 monitor
= DefaultProgressMonitor
.NewInstance();
228 int count
= dao
.count(clazz
);
229 monitor
.beginTask("update titles", count
);
231 for(int i
= 0 ; i
< count
; i
= i
+ stepSize
){
232 // not sure if such strict ordering is necessary here, but for safety reasons I do it
233 ArrayList
<OrderHint
> orderHints
= new ArrayList
<OrderHint
>();
234 orderHints
.add( new OrderHint("id", OrderHint
.SortOrder
.ASCENDING
));
235 List
<T
> list
= this.list(clazz
, stepSize
, i
, orderHints
, null);
236 List
<T
> entitiesToUpdate
= new ArrayList
<T
>();
237 for (T entity
: list
){
238 if (entity
.isProtectedTitleCache() == false){
239 updateTitleCacheForSingleEntity(cacheStrategy
, entitiesToUpdate
, entity
);
243 for (T entity
: entitiesToUpdate
){
244 if (entity
.getTitleCache() != null){
245 System
.err
.println(entity
.getTitleCache());
247 System
.err
.println("no titleCache" + ((NonViralName
)entity
).getNameCache());
251 saveOrUpdate(entitiesToUpdate
);
252 monitor
.worked(list
.size());
253 if (monitor
.isCanceled()){
262 * @param cacheStrategy
263 * @param entitiesToUpdate
266 private void updateTitleCacheForSingleEntity(
267 IIdentifiableEntityCacheStrategy
<T
> cacheStrategy
,
268 List
<T
> entitiesToUpdate
, T entity
) {
269 //exclude recursive inreferences
270 if (entity
.isInstanceOf(Reference
.class)){
271 Reference ref
= CdmBase
.deproxy(entity
, Reference
.class);
272 if (ref
.getInReference() != null && ref
.getInReference().equals(ref
)){
278 IIdentifiableEntityCacheStrategy entityCacheStrategy
= cacheStrategy
;
279 if (entityCacheStrategy
== null){
280 entityCacheStrategy
= entity
.getCacheStrategy();
281 //FIXME find out why the wrong cache strategy is loaded here, see #1876
282 if (entity
instanceof Reference
){
283 entityCacheStrategy
= ReferenceFactory
.newReference(((Reference
)entity
).getType()).getCacheStrategy();
286 entity
.setCacheStrategy(entityCacheStrategy
);
287 entity
.setProtectedTitleCache(true);
288 String titleCache
= entity
.getTitleCache();
289 entity
.setProtectedTitleCache(false);
290 String nameCache
= null;
291 if (entity
instanceof NonViralName
){
292 NonViralName nvn
= (NonViralName
) entity
;
293 if (!nvn
.isProtectedNameCache()){
294 nvn
.setProtectedNameCache(true);
295 nameCache
= nvn
.getNameCache();
296 nvn
.setProtectedNameCache(false);
301 setOtherCachesNull(entity
); //TODO find better solution
302 String newTitleCache
= entityCacheStrategy
.getTitleCache(entity
);
303 if (titleCache
== null || titleCache
!= null && ! titleCache
.equals(newTitleCache
) ){
304 entity
.setTitleCache(null, false);
305 entity
.getTitleCache();
306 if (entity
instanceof NonViralName
){
307 NonViralName nvn
= (NonViralName
) entity
;
308 String newnameCache
= nvn
.getNameCache();
310 entitiesToUpdate
.add(entity
);
311 }else if (entity
instanceof NonViralName
){
312 NonViralName nvn
= (NonViralName
) entity
;
313 String newnameCache
= nvn
.getNameCache();
314 if (nameCache
== null || (nameCache
!= null && !nameCache
.equals(newnameCache
))){
315 entitiesToUpdate
.add(entity
);
323 * Needs override if not only the title cache should be set to null to
324 * generate the correct new title cache
326 protected void setOtherCachesNull(T entity
) {
332 private class DeduplicateState
{
333 String lastTitleCache
;
334 Integer pageSize
= 50;
337 boolean isCompleted
= false;
342 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#deduplicate(java.lang.Class, eu.etaxonomy.cdm.strategy.match.IMatchStrategy, eu.etaxonomy.cdm.strategy.merge.IMergeStrategy)
345 @Transactional(propagation
= Propagation
.SUPPORTS
, readOnly
= false)
346 public int deduplicate(Class
<?
extends T
> clazz
, IMatchStrategy matchStrategy
, IMergeStrategy mergeStrategy
) {
347 DeduplicateState dedupState
= new DeduplicateState();
350 logger
.warn("Deduplication clazz must not be null!");
353 if (! ( IMatchable
.class.isAssignableFrom(clazz
) && IMergable
.class.isAssignableFrom(clazz
) ) ){
354 logger
.warn("Deduplication implemented only for classes implementing IMatchable and IMergeable. No deduplication performed!");
357 Class matchableClass
= clazz
;
358 if (matchStrategy
== null){
359 matchStrategy
= DefaultMatchStrategy
.NewInstance(matchableClass
);
361 List
<T
> nextGroup
= new ArrayList
<T
>();
364 // double countTotal = count(clazz);
366 // Number countPagesN = Math.ceil(countTotal/dedupState.pageSize.doubleValue()) ;
367 // int countPages = countPagesN.intValue();
370 List
<OrderHint
> orderHints
= Arrays
.asList(new OrderHint
[]{new OrderHint("titleCache", SortOrder
.ASCENDING
)});
372 while (! dedupState
.isCompleted
){
374 List
<T
> objectList
= getPages(clazz
, dedupState
, orderHints
);
375 //after each page check if any changes took place
376 int nUnEqualPages
= handleAllPages(objectList
, dedupState
, nextGroup
, matchStrategy
, mergeStrategy
);
377 nUnEqualPages
= nUnEqualPages
+ dedupState
.pageSize
* dedupState
.startPage
;
378 //refresh start page counter
379 int finishedPages
= nUnEqualPages
/ dedupState
.pageSize
;
380 dedupState
.startPage
= finishedPages
;
383 result
+= handleLastGroup(nextGroup
, matchStrategy
, mergeStrategy
);
388 private int handleAllPages(List
<T
> objectList
, DeduplicateState dedupState
, List
<T
> nextGroup
, IMatchStrategy matchStrategy
, IMergeStrategy mergeStrategy
) {
390 for (T object
: objectList
){
391 String currentTitleCache
= object
.getTitleCache();
392 if (currentTitleCache
!= null && currentTitleCache
.equals(dedupState
.lastTitleCache
)){
394 nextGroup
.add(object
);
397 dedupState
.result
+= handleLastGroup(nextGroup
, matchStrategy
, mergeStrategy
);
398 nextGroup
= new ArrayList
<T
>();
399 nextGroup
.add(object
);
402 dedupState
.lastTitleCache
= currentTitleCache
;
404 handleLastGroup(nextGroup
, matchStrategy
, mergeStrategy
);
408 private List
<T
> getPages(Class
<?
extends T
> clazz
, DeduplicateState dedupState
, List
<OrderHint
> orderHints
) {
409 List
<T
> result
= new ArrayList
<T
>();
410 for (int pageNo
= dedupState
.startPage
; pageNo
< dedupState
.startPage
+ dedupState
.nPages
; pageNo
++){
411 List
<T
> objectList
= listByTitle(clazz
, null, null, null, dedupState
.pageSize
, pageNo
, orderHints
, null);
412 result
.addAll(objectList
);
414 if (result
.size()< dedupState
.nPages
* dedupState
.pageSize
){
415 dedupState
.isCompleted
= true;
420 private int handleLastGroup(List
<T
> group
, IMatchStrategy matchStrategy
, IMergeStrategy mergeStrategy
) {
422 int size
= group
.size();
423 Set
<Integer
> exclude
= new HashSet
<Integer
>(); //set to collect all objects, that have been merged already
424 for (int i
= 0; i
< size
- 1; i
++){
425 if (exclude
.contains(i
)){
428 for (int j
= i
+ 1; j
< size
; j
++){
429 if (exclude
.contains(j
)){
432 T firstObject
= group
.get(i
);
433 T secondObject
= group
.get(j
);
436 if (matchStrategy
.invoke((IMatchable
)firstObject
, (IMatchable
)secondObject
)){
437 commonService
.merge((IMergable
)firstObject
, (IMergable
)secondObject
, mergeStrategy
);
441 } catch (MatchException e
) {
442 logger
.warn("MatchException when trying to match " + firstObject
.getTitleCache());
444 } catch (MergeException e
) {
445 logger
.warn("MergeException when trying to merge " + firstObject
.getTitleCache());
453 public Integer
countByTitle(Class
<?
extends T
> clazz
, String queryString
,MatchMode matchmode
, List
<Criterion
> criteria
){
454 Integer numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
456 return numberOfResults
;
459 @Transactional(readOnly
= true)
460 public Integer
countByTitle(IIdentifiableEntityServiceConfigurator
<T
> config
){
461 return countByTitle(config
.getClazz(), config
.getTitleSearchStringSqlized(),
462 config
.getMatchMode(), config
.getCriteria());