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
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
22 import org
.apache
.log4j
.Logger
;
23 import org
.hibernate
.criterion
.Criterion
;
24 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
25 import org
.springframework
.transaction
.annotation
.Transactional
;
27 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
28 import eu
.etaxonomy
.cdm
.api
.service
.config
.IIdentifiableEntityServiceConfigurator
;
29 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
30 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
31 import eu
.etaxonomy
.cdm
.common
.monitor
.DefaultProgressMonitor
;
32 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
33 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
34 import eu
.etaxonomy
.cdm
.model
.common
.ISourceable
;
35 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
36 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
37 import eu
.etaxonomy
.cdm
.model
.common
.LSID
;
38 import eu
.etaxonomy
.cdm
.model
.common
.UuidAndTitleCache
;
39 import eu
.etaxonomy
.cdm
.model
.media
.Rights
;
40 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
41 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
42 import eu
.etaxonomy
.cdm
.model
.reference
.ReferenceFactory
;
43 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IIdentifiableDao
;
44 import eu
.etaxonomy
.cdm
.persistence
.dao
.hibernate
.HibernateBeanInitializer
;
45 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AutoPropertyInitializer
;
46 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
47 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
48 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
49 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
50 import eu
.etaxonomy
.cdm
.strategy
.match
.DefaultMatchStrategy
;
51 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchStrategy
;
52 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchable
;
53 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchException
;
54 import eu
.etaxonomy
.cdm
.strategy
.merge
.IMergable
;
55 import eu
.etaxonomy
.cdm
.strategy
.merge
.IMergeStrategy
;
56 import eu
.etaxonomy
.cdm
.strategy
.merge
.MergeException
;
58 public abstract class IdentifiableServiceBase
<T
extends IdentifiableEntity
,DAO
extends IIdentifiableDao
<T
>> extends AnnotatableServiceBase
<T
,DAO
>
59 implements IIdentifiableEntityService
<T
>{
62 // protected ICommonService commonService;
65 protected static final int UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE
= 1000;
66 protected static final Logger logger
= Logger
.getLogger(IdentifiableServiceBase
.class);
68 @Transactional(readOnly
= true)
69 public Pager
<Rights
> getRights(T t
, Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
70 Integer numberOfResults
= dao
.countRights(t
);
72 List
<Rights
> results
= new ArrayList
<Rights
>();
73 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
74 results
= dao
.getRights(t
, pageSize
, pageNumber
,propertyPaths
);
77 return new DefaultPagerImpl
<Rights
>(pageNumber
, numberOfResults
, pageSize
, results
);
80 @Transactional(readOnly
= true)
81 public Pager
<IdentifiableSource
> getSources(T t
, Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
82 Integer numberOfResults
= dao
.countSources(t
);
84 List
<IdentifiableSource
> results
= new ArrayList
<IdentifiableSource
>();
85 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
86 results
= dao
.getSources(t
, pageSize
, pageNumber
,propertyPaths
);
89 return new DefaultPagerImpl
<IdentifiableSource
>(pageNumber
, numberOfResults
, pageSize
, results
);
93 @Transactional(readOnly
= false)
94 public T
replace(T x
, T y
) {
95 return dao
.replace(x
, y
);
98 * FIXME Candidate for harmonization
99 * Given that this method is strongly typed, and generic, could we not simply expose it as
100 * List<T> findByTitle(String title) as it is somewhat less cumbersome. Admittedly, I don't
101 * understand what is going on with the configurators etc. so maybe there is a good reason for
102 * the design of this method.
106 @Transactional(readOnly
= true)
107 protected List
<T
> findCdmObjectsByTitle(String title
){
108 return ((IIdentifiableDao
)dao
).findByTitle(title
);
111 @Transactional(readOnly
= true)
112 protected List
<T
> findCdmObjectsByTitle(String title
, Class
<T
> clazz
){
113 return ((IIdentifiableDao
)dao
).findByTitleAndClass(title
, clazz
);
115 @Transactional(readOnly
= true)
116 protected List
<T
> findCdmObjectsByTitle(String title
, CdmBase sessionObject
){
117 return ((IIdentifiableDao
)dao
).findByTitle(title
, sessionObject
);
121 * TODO - Migrated from CommonServiceBase
123 * @see eu.etaxonomy.cdm.api.service.ICommonService#getSourcedObjectById(java.lang.String, java.lang.String)
125 @Transactional(readOnly
= true)
126 public ISourceable
getSourcedObjectByIdInSource(Class clazz
, String idInSource
, String idNamespace
) {
127 ISourceable result
= null;
129 List
<T
> list
= dao
.findOriginalSourceByIdInSource(idInSource
, idNamespace
);
130 if (! list
.isEmpty()){
131 result
= list
.get(0);
137 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#getUuidAndTitleCache()
139 @Transactional(readOnly
= true)
140 public List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache() {
141 return dao
.getUuidAndTitleCache();
144 @Transactional(readOnly
= true)
145 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
) {
146 Integer numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
148 List
<T
> results
= new ArrayList
<T
>();
149 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
150 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
153 return new DefaultPagerImpl
<T
>(pageNumber
, numberOfResults
, pageSize
, results
);
156 @Transactional(readOnly
= true)
157 public Pager
<T
> findByTitle(IIdentifiableEntityServiceConfigurator
<T
> config
){
158 return findByTitle(config
.getClazz(), config
.getTitleSearchStringSqlized(), config
.getMatchMode(), config
.getCriteria(), config
.getPageSize(), config
.getPageNumber(), config
.getOrderHints(), config
.getPropertyPaths());
161 @Transactional(readOnly
= true)
162 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
) {
163 Integer numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
165 List
<T
> results
= new ArrayList
<T
>();
166 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
167 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
172 @Transactional(readOnly
= true)
173 public Pager
<T
> findTitleCache(Class
<?
extends T
> clazz
, String queryString
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, MatchMode matchMode
){
174 long numberOfResults
= dao
.countTitleCache(clazz
, queryString
, matchMode
);
176 List
<T
> results
= new ArrayList
<T
>();
177 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
178 results
= dao
.findTitleCache(clazz
, queryString
, pageSize
, pageNumber
, orderHints
, matchMode
);
181 r
+= numberOfResults
;
183 return new DefaultPagerImpl
<T
>(pageNumber
, r
, pageSize
, results
);
186 @Transactional(readOnly
= true)
187 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
) {
188 Integer numberOfResults
= dao
.countByReferenceTitle(clazz
, queryString
, matchmode
, criteria
);
190 List
<T
> results
= new ArrayList
<T
>();
191 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
192 results
= dao
.findByReferenceTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
197 @Transactional(readOnly
= true)
198 public T
find(LSID lsid
) {
199 return dao
.find(lsid
);
202 @Transactional(readOnly
= true)
203 public Pager
<T
> search(Class
<?
extends T
> clazz
, String queryString
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
204 Integer numberOfResults
= dao
.count(clazz
,queryString
);
206 List
<T
> results
= new ArrayList
<T
>();
207 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
208 results
= dao
.search(clazz
,queryString
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
211 return new DefaultPagerImpl
<T
>(pageNumber
, numberOfResults
, pageSize
, results
);
216 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache()
219 @Transactional(readOnly
= false)
220 public void updateTitleCache() {
221 updateTitleCache(null, null, null, null);
224 @Transactional(readOnly
= false) //TODO check transactional behaviour, e.g. what happens with the session if count is very large
225 protected <S
extends T
> void updateTitleCacheImpl(Class
<S
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<T
> cacheStrategy
, IProgressMonitor monitor
) {
226 if (stepSize
== null){
227 stepSize
= UPDATE_TITLE_CACHE_DEFAULT_STEP_SIZE
;
229 if (monitor
== null){
230 monitor
= DefaultProgressMonitor
.NewInstance();
233 int count
= dao
.count(clazz
);
234 monitor
.beginTask("update titles", count
);
236 for(int i
= 0 ; i
< count
; i
= i
+ stepSize
){
237 // not sure if such strict ordering is necessary here, but for safety reasons I do it
238 ArrayList
<OrderHint
> orderHints
= new ArrayList
<OrderHint
>();
239 orderHints
.add( new OrderHint("id", OrderHint
.SortOrder
.ASCENDING
));
242 Map
<Class
<?
extends CdmBase
>, AutoPropertyInitializer
<CdmBase
>> oldAutoInit
= switchOfAutoinitializer();
243 List
<S
> list
= this.list(clazz
, stepSize
, i
, orderHints
, null);
244 switchOnOldAutoInitializer(oldAutoInit
);
246 List
<T
> entitiesToUpdate
= new ArrayList
<T
>();
247 for (T entity
: list
){
248 if (entity
.isProtectedTitleCache() == false){
249 updateTitleCacheForSingleEntity(cacheStrategy
, entitiesToUpdate
, entity
);
253 for (T entity
: entitiesToUpdate
){
254 if (entity
.getTitleCache() != null){
255 //System.err.println(entity.getTitleCache());
257 //System.err.println("no titleCache" + ((NonViralName)entity).getNameCache());
260 saveOrUpdate(entitiesToUpdate
);
261 monitor
.worked(list
.size());
262 if (monitor
.isCanceled()){
271 * Brings back all auto initializers to the bean initializer
272 * @see #switchOfAutoinitializer()
275 protected void switchOnOldAutoInitializer(
276 Map
<Class
<?
extends CdmBase
>, AutoPropertyInitializer
<CdmBase
>> oldAutoInit
) {
277 HibernateBeanInitializer initializer
= (HibernateBeanInitializer
)this.appContext
.getBean("defaultBeanInitializer");
278 initializer
.setBeanAutoInitializers(oldAutoInit
);
282 * Removes all auto initializers from the bean initializer
284 * @see #switchOnOldAutoInitializer(Map)
287 protected Map
<Class
<?
extends CdmBase
>, AutoPropertyInitializer
<CdmBase
>> switchOfAutoinitializer() {
288 HibernateBeanInitializer initializer
= (HibernateBeanInitializer
)this.appContext
.getBean("defaultBeanInitializer");
289 Map
<Class
<?
extends CdmBase
>, AutoPropertyInitializer
<CdmBase
>> oldAutoInitializers
= initializer
.getBeanAutoInitializers();
290 Map
<Class
<?
extends CdmBase
>, AutoPropertyInitializer
<CdmBase
>> map
= new HashMap
<Class
<?
extends CdmBase
>, AutoPropertyInitializer
<CdmBase
>>();
291 initializer
.setBeanAutoInitializers(map
);
292 return oldAutoInitializers
;
296 * @param cacheStrategy
297 * @param entitiesToUpdate
301 * @param cacheStrategy
302 * @param entitiesToUpdate
305 private void updateTitleCacheForSingleEntity(
306 IIdentifiableEntityCacheStrategy
<T
> cacheStrategy
,
307 List
<T
> entitiesToUpdate
, T entity
) {
309 assert (entity
.isProtectedTitleCache() == false );
311 //exclude recursive inreferences
312 if (entity
.isInstanceOf(Reference
.class)){
313 Reference
<?
> ref
= CdmBase
.deproxy(entity
, Reference
.class);
314 if (ref
.getInReference() != null && ref
.getInReference().equals(ref
)){
320 //define the correct cache strategy
321 IIdentifiableEntityCacheStrategy entityCacheStrategy
= cacheStrategy
;
322 if (entityCacheStrategy
== null){
323 entityCacheStrategy
= entity
.getCacheStrategy();
324 //FIXME find out why the wrong cache strategy is loaded here, see #1876
325 if (entity
instanceof Reference
){
326 entityCacheStrategy
= ReferenceFactory
.newReference(((Reference
)entity
).getType()).getCacheStrategy();
329 entity
.setCacheStrategy(entityCacheStrategy
);
333 entity
.setProtectedTitleCache(true);
334 String oldTitleCache
= entity
.getTitleCache();
335 entity
.setTitleCache(oldTitleCache
, false); //before we had entity.setProtectedTitleCache(false) but this deleted the titleCache itself
337 //NonViralNames have more caches //TODO handle in NameService
338 String oldNameCache
= null;
339 String oldFullTitleCache
= null;
340 if (entity
instanceof NonViralName
){
341 NonViralName
<?
> nvn
= (NonViralName
) entity
;
342 if (!nvn
.isProtectedNameCache()){
343 nvn
.setProtectedNameCache(true);
344 oldNameCache
= nvn
.getNameCache();
345 nvn
.setProtectedNameCache(false);
347 if (!nvn
.isProtectedFullTitleCache()){
348 nvn
.setProtectedFullTitleCache(true);
349 oldFullTitleCache
= nvn
.getFullTitleCache();
350 nvn
.setProtectedFullTitleCache(false);
353 setOtherCachesNull(entity
); //TODO find better solution
355 String newTitleCache
= entityCacheStrategy
.getTitleCache(entity
);
356 if (oldTitleCache
== null || oldTitleCache
!= null && ! oldTitleCache
.equals(newTitleCache
) ){
357 entity
.setTitleCache(null, false);
358 String newCache
= entity
.getTitleCache();
359 if (newCache
== null){
360 logger
.warn("newCache should never be null");
362 if (oldTitleCache
== null){
363 logger
.info("oldTitleCache should never be null");
365 if (entity
instanceof NonViralName
){
366 NonViralName
<?
> nvn
= (NonViralName
) entity
;
368 nvn
.getFullTitleCache();
370 entitiesToUpdate
.add(entity
);
371 }else if (entity
instanceof NonViralName
){
372 NonViralName
<?
> nvn
= (NonViralName
) entity
;
373 String newnameCache
= nvn
.getNameCache();
374 String newFullTitleCache
= nvn
.getFullTitleCache();
375 if (oldNameCache
== null || (oldNameCache
!= null && !oldNameCache
.equals(newnameCache
))){
376 entitiesToUpdate
.add(entity
);
377 }else if (oldFullTitleCache
== null || (oldFullTitleCache
!= null && !oldFullTitleCache
.equals(newFullTitleCache
))){
378 entitiesToUpdate
.add(entity
);
386 * Needs override if not only the title cache should be set to null to
387 * generate the correct new title cache
389 protected void setOtherCachesNull(T entity
) {
395 private class DeduplicateState
{
396 String lastTitleCache
;
397 Integer pageSize
= 50;
400 boolean isCompleted
= false;
405 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#deduplicate(java.lang.Class, eu.etaxonomy.cdm.strategy.match.IMatchStrategy, eu.etaxonomy.cdm.strategy.merge.IMergeStrategy)
408 @Transactional(readOnly
= false)
409 public int deduplicate(Class
<?
extends T
> clazz
, IMatchStrategy matchStrategy
, IMergeStrategy mergeStrategy
) {
410 DeduplicateState dedupState
= new DeduplicateState();
413 logger
.warn("Deduplication clazz must not be null!");
416 if (! ( IMatchable
.class.isAssignableFrom(clazz
) && IMergable
.class.isAssignableFrom(clazz
) ) ){
417 logger
.warn("Deduplication implemented only for classes implementing IMatchable and IMergeable. No deduplication performed!");
420 Class matchableClass
= clazz
;
421 if (matchStrategy
== null){
422 matchStrategy
= DefaultMatchStrategy
.NewInstance(matchableClass
);
424 List
<T
> nextGroup
= new ArrayList
<T
>();
427 // double countTotal = count(clazz);
429 // Number countPagesN = Math.ceil(countTotal/dedupState.pageSize.doubleValue()) ;
430 // int countPages = countPagesN.intValue();
433 List
<OrderHint
> orderHints
= Arrays
.asList(new OrderHint
[]{new OrderHint("titleCache", SortOrder
.ASCENDING
)});
435 while (! dedupState
.isCompleted
){
437 List
<T
> objectList
= getPages(clazz
, dedupState
, orderHints
);
438 //after each page check if any changes took place
439 int nUnEqualPages
= handleAllPages(objectList
, dedupState
, nextGroup
, matchStrategy
, mergeStrategy
);
440 nUnEqualPages
= nUnEqualPages
+ dedupState
.pageSize
* dedupState
.startPage
;
441 //refresh start page counter
442 int finishedPages
= nUnEqualPages
/ dedupState
.pageSize
;
443 dedupState
.startPage
= finishedPages
;
446 result
+= handleLastGroup(nextGroup
, matchStrategy
, mergeStrategy
);
451 private int handleAllPages(List
<T
> objectList
, DeduplicateState dedupState
, List
<T
> nextGroup
, IMatchStrategy matchStrategy
, IMergeStrategy mergeStrategy
) {
453 for (T object
: objectList
){
454 String currentTitleCache
= object
.getTitleCache();
455 if (currentTitleCache
!= null && currentTitleCache
.equals(dedupState
.lastTitleCache
)){
457 nextGroup
.add(object
);
460 dedupState
.result
+= handleLastGroup(nextGroup
, matchStrategy
, mergeStrategy
);
461 nextGroup
= new ArrayList
<T
>();
462 nextGroup
.add(object
);
465 dedupState
.lastTitleCache
= currentTitleCache
;
467 handleLastGroup(nextGroup
, matchStrategy
, mergeStrategy
);
471 private List
<T
> getPages(Class
<?
extends T
> clazz
, DeduplicateState dedupState
, List
<OrderHint
> orderHints
) {
472 List
<T
> result
= new ArrayList
<T
>();
473 for (int pageNo
= dedupState
.startPage
; pageNo
< dedupState
.startPage
+ dedupState
.nPages
; pageNo
++){
474 List
<T
> objectList
= listByTitle(clazz
, null, null, null, dedupState
.pageSize
, pageNo
, orderHints
, null);
475 result
.addAll(objectList
);
477 if (result
.size()< dedupState
.nPages
* dedupState
.pageSize
){
478 dedupState
.isCompleted
= true;
483 private int handleLastGroup(List
<T
> group
, IMatchStrategy matchStrategy
, IMergeStrategy mergeStrategy
) {
485 int size
= group
.size();
486 Set
<Integer
> exclude
= new HashSet
<Integer
>(); //set to collect all objects, that have been merged already
487 for (int i
= 0; i
< size
- 1; i
++){
488 if (exclude
.contains(i
)){
491 for (int j
= i
+ 1; j
< size
; j
++){
492 if (exclude
.contains(j
)){
495 T firstObject
= group
.get(i
);
496 T secondObject
= group
.get(j
);
499 if (matchStrategy
.invoke((IMatchable
)firstObject
, (IMatchable
)secondObject
)){
500 commonService
.merge((IMergable
)firstObject
, (IMergable
)secondObject
, mergeStrategy
);
504 } catch (MatchException e
) {
505 logger
.warn("MatchException when trying to match " + firstObject
.getTitleCache());
507 } catch (MergeException e
) {
508 logger
.warn("MergeException when trying to merge " + firstObject
.getTitleCache());
516 public Integer
countByTitle(Class
<?
extends T
> clazz
, String queryString
,MatchMode matchmode
, List
<Criterion
> criteria
){
517 Integer numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
519 return numberOfResults
;
522 @Transactional(readOnly
= true)
523 public Integer
countByTitle(IIdentifiableEntityServiceConfigurator
<T
> config
){
524 return countByTitle(config
.getClazz(), config
.getTitleSearchStringSqlized(),
525 config
.getMatchMode(), config
.getCriteria());