Project

General

Profile

Download (77.7 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2007 EDIT
3
 * European Distributed Institute of Taxonomy 
4
 * http://www.e-taxonomy.eu
5
 * 
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9
package eu.etaxonomy.cdm.persistence.dao.hibernate.taxon;
10

    
11
import java.util.ArrayList;
12
import java.util.Collection;
13
import java.util.Collections;
14
import java.util.Comparator;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.Iterator;
18
import java.util.List;
19
import java.util.Set;
20
import java.util.SortedSet;
21
import java.util.TreeSet;
22
import java.util.UUID;
23

    
24
import org.apache.log4j.Logger;
25
import org.apache.lucene.queryParser.ParseException;
26
import org.hibernate.Criteria;
27
import org.hibernate.FetchMode;
28
import org.hibernate.Hibernate;
29
import org.hibernate.Query;
30
import org.hibernate.Session;
31
import org.hibernate.criterion.Criterion;
32
import org.hibernate.criterion.Projections;
33
import org.hibernate.criterion.Restrictions;
34
import org.hibernate.envers.query.AuditEntity;
35
import org.hibernate.envers.query.AuditQuery;
36
import org.hibernate.search.FullTextSession;
37
import org.hibernate.search.Search;
38
import org.springframework.beans.factory.annotation.Autowired;
39
import org.springframework.beans.factory.annotation.Qualifier;
40
import org.springframework.dao.DataAccessException;
41
import org.springframework.stereotype.Repository;
42

    
43
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
44
import eu.etaxonomy.cdm.model.common.CdmBase;
45
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
46
import eu.etaxonomy.cdm.model.common.LSID;
47
import eu.etaxonomy.cdm.model.common.OriginalSourceBase;
48
import eu.etaxonomy.cdm.model.common.RelationshipBase;
49
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
50
import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
51
import eu.etaxonomy.cdm.model.location.NamedArea;
52
import eu.etaxonomy.cdm.model.name.NonViralName;
53
import eu.etaxonomy.cdm.model.name.Rank;
54
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
55
import eu.etaxonomy.cdm.model.name.TaxonNameComparator;
56
import eu.etaxonomy.cdm.model.name.ZoologicalName;
57
import eu.etaxonomy.cdm.model.reference.Reference;
58
import eu.etaxonomy.cdm.model.taxon.Classification;
59
import eu.etaxonomy.cdm.model.taxon.Synonym;
60
import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
61
import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
62
import eu.etaxonomy.cdm.model.taxon.Taxon;
63
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
64
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
65
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
66
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
67
import eu.etaxonomy.cdm.model.view.AuditEvent;
68
import eu.etaxonomy.cdm.persistence.dao.QueryParseException;
69
import eu.etaxonomy.cdm.persistence.dao.hibernate.AlternativeSpellingSuggestionParser;
70
import eu.etaxonomy.cdm.persistence.dao.hibernate.common.IdentifiableDaoBase;
71
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
72
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
73
import eu.etaxonomy.cdm.persistence.fetch.CdmFetch;
74
import eu.etaxonomy.cdm.persistence.query.MatchMode;
75
import eu.etaxonomy.cdm.persistence.query.OrderHint;
76
import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
77

    
78

    
79
/**
80
 * @author a.mueller
81
 * @created 24.11.2008
82
 * @version 1.0
83
 */
84
@Repository
85
@Qualifier("taxonDaoHibernateImpl")
86
public class TaxonDaoHibernateImpl extends IdentifiableDaoBase<TaxonBase> implements ITaxonDao {	
87
	private AlternativeSpellingSuggestionParser<TaxonBase> alternativeSpellingSuggestionParser;
88
	private static final Logger logger = Logger.getLogger(TaxonDaoHibernateImpl.class);
89

    
90
	public TaxonDaoHibernateImpl() {
91
		super(TaxonBase.class);
92
		indexedClasses = new Class[2];
93
		indexedClasses[0] = Taxon.class;
94
		indexedClasses[1] = Synonym.class;
95
		super.defaultField = "name.titleCache_tokenized";
96
	}
97
	
98
	@Autowired
99
	private ITaxonNameDao taxonNameDao;
100
	
101
	@Autowired(required = false)   //TODO switched of because it caused problems when starting CdmApplicationController
102
	public void setAlternativeSpellingSuggestionParser(AlternativeSpellingSuggestionParser<TaxonBase> alternativeSpellingSuggestionParser) {
103
		this.alternativeSpellingSuggestionParser = alternativeSpellingSuggestionParser; 
104
	}
105
	
106

    
107
	/* (non-Javadoc)
108
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getRootTaxa(eu.etaxonomy.cdm.model.reference.Reference)
109
	 */
110
	public List<Taxon> getRootTaxa(Reference sec) {
111
		return getRootTaxa(sec, CdmFetch.FETCH_CHILDTAXA(), true, false);
112
	}
113
		
114
	/* (non-Javadoc)
115
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getRootTaxa(eu.etaxonomy.cdm.model.name.Rank, eu.etaxonomy.cdm.model.reference.Reference, eu.etaxonomy.cdm.persistence.fetch.CdmFetch, java.lang.Boolean, java.lang.Boolean)
116
	 */
117
	public List<Taxon> getRootTaxa(Rank rank, Reference sec, CdmFetch cdmFetch, Boolean onlyWithChildren, Boolean withMisapplications, List<String> propertyPaths) {
118
		checkNotInPriorView("TaxonDaoHibernateImpl.getRootTaxa(Rank rank, Reference sec, CdmFetch cdmFetch, Boolean onlyWithChildren, Boolean withMisapplications)");
119
		if (onlyWithChildren == null){
120
			onlyWithChildren = true;
121
		}
122
		if (withMisapplications == null){
123
			withMisapplications = true;
124
		}
125
		if (cdmFetch == null){
126
			cdmFetch = CdmFetch.NO_FETCH();
127
		}
128

    
129
		Criteria crit = getSession().createCriteria(Taxon.class);
130
		
131
		crit.setFetchMode("name", FetchMode.JOIN);
132
		crit.createAlias("name", "name");
133
		
134
		if (rank != null) {
135
			crit.add(Restrictions.eq("name.rank", rank));
136
		}else{
137
			crit.add(Restrictions.isNull("taxonomicParentCache"));
138
		}
139

    
140
		if (sec != null){
141
			crit.add(Restrictions.eq("sec", sec) );
142
		}
143

    
144
		if (! cdmFetch.includes(CdmFetch.FETCH_CHILDTAXA())){
145
			logger.info("Not fetching child taxa");
146
			//TODO overwrite LAZY (SELECT) does not work (bug in hibernate?)
147
			crit.setFetchMode("relationsToThisTaxon.fromTaxon", FetchMode.LAZY);
148
		}
149

    
150
		List<Taxon> results = new ArrayList<Taxon>();
151
		List<Taxon> taxa = crit.list();
152
		for(Taxon taxon : taxa){
153
			
154
			
155
			//childTaxa
156
			//TODO create restriction instead
157
			// (a) not using cache fields
158
			/*Hibernate.initialize(taxon.getRelationsFromThisTaxon());
159
			if (onlyWithChildren == false || taxon.getRelationsFromThisTaxon().size() > 0){
160
				if (withMisapplications == true || ! taxon.isMisappliedName()){
161
					defaultBeanInitializer.initialize(taxon, propertyPaths);
162
					results.add(taxon);
163
				}
164
			}*/
165
			// (b) using cache fields
166
			if (onlyWithChildren == false || taxon.hasTaxonomicChildren()){
167
				if (withMisapplications == true || ! taxon.isMisapplication()){
168
					defaultBeanInitializer.initialize(taxon, propertyPaths);
169
					results.add(taxon);
170
				}
171
			}
172
		}
173
		return results;
174
	}
175

    
176
	/* (non-Javadoc)
177
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getRootTaxa(eu.etaxonomy.cdm.model.reference.Reference, eu.etaxonomy.cdm.persistence.fetch.CdmFetch, java.lang.Boolean, java.lang.Boolean)
178
	 */
179
	public List<Taxon> getRootTaxa(Reference sec, CdmFetch cdmFetch, Boolean onlyWithChildren, Boolean withMisapplications) {
180
		return getRootTaxa(null, sec, cdmFetch, onlyWithChildren, withMisapplications, null);
181
	}
182
	
183
	/*
184
	 * (non-Javadoc)
185
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getTaxaByName(java.lang.String, eu.etaxonomy.cdm.model.reference.Reference)
186
	 */
187
	public List<TaxonBase> getTaxaByName(String queryString, Reference sec) {
188
		
189
		return getTaxaByName(queryString, true, sec);
190
	}
191

    
192
	/*
193
	 * (non-Javadoc)
194
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getTaxaByName(java.lang.String, java.lang.Boolean, eu.etaxonomy.cdm.model.reference.Reference)
195
	 */
196
	public List<TaxonBase> getTaxaByName(String queryString, Boolean accepted, Reference sec) {
197
		checkNotInPriorView("TaxonDaoHibernateImpl.getTaxaByName(String name, Reference sec)");
198
		
199
        Criteria criteria = null;
200
		if (accepted == true) {
201
			criteria = getSession().createCriteria(Taxon.class);
202
		} else {
203
			criteria = getSession().createCriteria(Synonym.class);
204
		}
205
		
206
		criteria.setFetchMode( "name", FetchMode.JOIN );
207
		criteria.createAlias("name", "name");
208
		
209
		if (sec != null && sec.getId() != 0) {
210
			criteria.add(Restrictions.eq("sec", sec ) );
211
		}
212

    
213
		if (queryString != null) {
214
			criteria.add(Restrictions.ilike("name.nameCache", queryString));
215
		}
216

    
217
		return (List<TaxonBase>)criteria.list();
218
	}
219

    
220
	public List<TaxonBase> getTaxaByName(Class<? extends TaxonBase> clazz, String queryString, MatchMode matchMode,
221
			Integer pageSize, Integer pageNumber) {
222
		
223
		return getTaxaByName(clazz, queryString, null, matchMode, null, pageSize, pageNumber, null);
224
	}
225
	
226
	/*
227
	 * (non-Javadoc)
228
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getTaxaByName(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, java.lang.Boolean, java.lang.Integer, java.lang.Integer)
229
	 */
230
	public List<TaxonBase> getTaxaByName(String queryString, MatchMode matchMode, 
231
			Boolean accepted, Integer pageSize, Integer pageNumber) {
232
		
233
		if (accepted == true) {
234
			return getTaxaByName(Taxon.class, queryString, matchMode, pageSize, pageNumber);
235
		} else {
236
			return getTaxaByName(Synonym.class, queryString, matchMode, pageSize, pageNumber);
237
		}
238
	}
239
	
240
	/*
241
	 * (non-Javadoc)
242
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getTaxaByName(java.lang.Class, java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, eu.etaxonomy.cdm.persistence.query.MatchMode, java.util.Set, java.lang.Integer, java.lang.Integer, java.util.List)
243
	 */
244
	public List<TaxonBase> getTaxaByName(Class<? extends TaxonBase> clazz, String queryString, Classification classification,
245
			MatchMode matchMode, Set<NamedArea> namedAreas, Integer pageSize, 
246
			Integer pageNumber, List<String> propertyPaths) {
247
				
248
		boolean doCount = false;
249
			
250
		Query query = prepareTaxaByName(clazz, "nameCache", queryString, classification, matchMode, namedAreas, pageSize, pageNumber, doCount);
251
		
252
		if (query != null){
253
			List<TaxonBase> results = query.list();
254
			
255
			defaultBeanInitializer.initializeAll(results, propertyPaths);
256
			//TaxonComparatorSearch comp = new TaxonComparatorSearch();
257
			//Collections.sort(results, comp);
258
			return results;
259
		}
260
		
261
		return new ArrayList<TaxonBase>();
262
		
263
	}
264

    
265
	/*
266
	 * (non-Javadoc)
267
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getTaxaByName(java.lang.Class, java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, eu.etaxonomy.cdm.persistence.query.MatchMode, java.util.Set, java.lang.Integer, java.lang.Integer, java.util.List)
268
	 */
269
	//new search for the editor, for performance issues the return values are only uuid and titleCache, to avoid the initialisation of all objects
270
	@SuppressWarnings("unchecked")
271
	public List<UuidAndTitleCache<TaxonBase>> getTaxaByNameForEditor(Class<? extends TaxonBase> clazz, String queryString, Classification classification,
272
			MatchMode matchMode, Set<NamedArea> namedAreas) {
273
		long zstVorher;
274
		long zstNachher;
275
				
276
		boolean doCount = false;
277
		Query query = prepareTaxaByNameForEditor(clazz, "nameCache", queryString, classification, matchMode, namedAreas, doCount);
278
		
279
		
280
		if (query != null){
281
			List<Object[]> results = query.list();
282
			
283
			List<UuidAndTitleCache<TaxonBase>> resultObjects = new ArrayList<UuidAndTitleCache<TaxonBase>>();
284
			Object[] result;
285
			for(int i = 0; i<results.size();i++){
286
				result = results.get(i);
287
				
288
				//unterscheiden von taxa und synonymen
289
				if (clazz.equals(Taxon.class)){
290
						resultObjects.add( new UuidAndTitleCache(Taxon.class, (UUID) result[0], (String)result[1]));
291
				}else if (clazz.equals(Synonym.class)){
292
					resultObjects.add( new UuidAndTitleCache(Synonym.class, (UUID) result[0], (String)result[1]));
293
				} else{
294
					if (result[2].equals("synonym")) {
295
						resultObjects.add( new UuidAndTitleCache(Synonym.class, (UUID) result[0], (String)result[1]));
296
					}
297
					else {
298
						resultObjects.add( new UuidAndTitleCache(Taxon.class, (UUID) result[0], (String)result[1]));
299
					}
300
				}
301
			}
302
			
303
			return resultObjects;
304
			
305
		}
306
		return new ArrayList<UuidAndTitleCache<TaxonBase>>();
307
		
308
	}
309
	
310
	/*
311
	 * (non-Javadoc)
312
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getTaxaByCommonName(java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, eu.etaxonomy.cdm.persistence.query.MatchMode, java.util.Set, java.lang.Integer, java.lang.Integer, java.util.List)
313
	 */
314
	public List<TaxonBase> getTaxaByCommonName(String queryString, Classification classification,
315
			MatchMode matchMode, Set<NamedArea> namedAreas, Integer pageSize, 
316
			Integer pageNumber, List<String> propertyPaths) {
317
			boolean doCount = false;	
318
			Query query = prepareTaxaByCommonName(queryString, classification, matchMode, namedAreas, pageSize, pageNumber, doCount);
319
			if (query != null){
320
				List<TaxonBase> results = query.list();
321
				defaultBeanInitializer.initializeAll(results, propertyPaths);
322
				return results;
323
			}
324
			return new ArrayList<TaxonBase>();
325
		
326
	}
327
	
328
	/**
329
	 * @param clazz
330
	 * @param searchField the field in TaxonNameBase to be searched through usually either <code>nameCache</code> or <code>titleCache</code>
331
	 * @param queryString
332
	 * @param classification TODO
333
	 * @param matchMode
334
	 * @param namedAreas
335
	 * @param pageSize
336
	 * @param pageNumber
337
	 * @param doCount
338
	 * @return
339
	 * 
340
	 *
341
	 */
342
	private Query prepareTaxaByNameForEditor(Class<? extends TaxonBase> clazz, String searchField, String queryString, Classification classification,
343
			MatchMode matchMode, Set<NamedArea> namedAreas, boolean doCount) {
344
		return prepareQuery(clazz, searchField, queryString, classification,
345
				matchMode, namedAreas, doCount, true);
346
	}
347
	
348
	/**
349
	 * @param clazz
350
	 * @param searchField
351
	 * @param queryString
352
	 * @param classification
353
	 * @param matchMode
354
	 * @param namedAreas
355
	 * @param doCount
356
	 * @param doNotReturnFullEntities
357
	 *            if set true the seach method will not return synonym and taxon
358
	 *            entities but an array containing the uuid, titleCache, and the
359
	 *            DTYPE in lowercase letters.
360
	 * @return
361
	 */
362
	private Query prepareQuery(Class<? extends TaxonBase> clazz, String searchField, String queryString, Classification classification,
363
			MatchMode matchMode, Set<NamedArea> namedAreas, boolean doCount, boolean doNotReturnFullEntities){
364
		
365
		String hqlQueryString = matchMode.queryStringFrom(queryString);
366
		String selectWhat;
367
		if (doNotReturnFullEntities){
368
			selectWhat = "t.uuid, t.titleCache ";
369
		}else {
370
			selectWhat = (doCount ? "count(t)": "t");
371
		}
372
		
373
		String hql = "";
374
		Set<NamedArea> areasExpanded = new HashSet<NamedArea>();
375
		if(namedAreas != null && namedAreas.size() > 0){
376
			// expand areas and restrict by distribution area
377
			List<NamedArea> childAreas;
378
			Query areaQuery = getSession().createQuery("select childArea from NamedArea as childArea left join childArea.partOf as parentArea where parentArea = :area");
379
			expandNamedAreas(namedAreas, areasExpanded, areaQuery);
380
		}
381
		boolean doAreaRestriction = areasExpanded.size() > 0;
382
		
383
		Set<UUID> namedAreasUuids = new HashSet<UUID>();
384
		for (NamedArea area:areasExpanded){
385
			namedAreasUuids.add(area.getUuid());
386
		}
387
		
388
		String taxonSubselect = null;
389
		String synonymSubselect = null;
390
		
391
		if(classification != null){
392
			
393
			if(doAreaRestriction){
394
				
395
				taxonSubselect = "select t.id from" +
396
					" Distribution e" +
397
					" join e.inDescription d" +
398
					" join d.taxon t" +
399
					" join t.name n " +
400
					" join t.taxonNodes as tn "+
401
					" where" +
402
					" e.area.uuid in (:namedAreasUuids) AND" +
403
					" tn.classification = :classification" +
404
					" AND n." + searchField + " " + matchMode.getMatchOperator() + " :queryString";
405
				
406
				
407
				synonymSubselect = "select s.id from" +
408
					" Distribution e" +
409
					" join e.inDescription d" +
410
					" join d.taxon t" + // the taxa
411
					" join t.taxonNodes as tn "+
412
					" join t.synonymRelations sr" +
413
					" join sr.relatedFrom s" + // the synonyms
414
					" join s.name sn"+ 
415
					" where" +
416
					" e.area.uuid in (:namedAreasUuids) AND" +
417
					" tn.classification = :classification" +
418
					" AND sn." + searchField +  " " + matchMode.getMatchOperator() + " :queryString";
419
				
420
			} else {
421
				
422
				taxonSubselect = "select t.id from" +
423
					" Taxon t" +
424
					" join t.name n " +
425
					" join t.taxonNodes as tn "+
426
					" where" +
427
					" tn.classification = :classification" +
428
					" AND n." + searchField +  " " + matchMode.getMatchOperator() + " :queryString";
429
				
430
				synonymSubselect = "select s.id from" +
431
					" Taxon t" + // the taxa
432
					" join t.taxonNodes as tn "+
433
					" join t.synonymRelations sr" +
434
					" join sr.relatedFrom s" + // the synonyms
435
					" join s.name sn"+ 
436
					" where" +
437
					" tn.classification = :classification" +
438
					" AND sn." + searchField +  " " + matchMode.getMatchOperator() + " :queryString";
439
			}	
440
		} else {
441
			
442
			if(doAreaRestriction){
443
				
444
				taxonSubselect = "select t.id from " +
445
					" Distribution e" +
446
					" join e.inDescription d" +
447
					" join d.taxon t" +
448
					" join t.name n "+
449
					" where" +
450
					(doAreaRestriction ? " e.area.uuid in (:namedAreasUuids) AND" : "") +
451
					" n." + searchField +  " " + matchMode.getMatchOperator() + " :queryString";
452
				
453
				synonymSubselect = "select s.id from" +
454
					" Distribution e" +
455
					" join e.inDescription d" +
456
					" join d.taxon t" + // the taxa
457
					" join t.synonymRelations sr" +
458
					" join sr.relatedFrom s" + // the synonyms
459
					" join s.name sn"+ 
460
					" where" +
461
					(doAreaRestriction ? " e.area.uuid in (:namedAreasUuids) AND" : "") +
462
					" sn." + searchField +  " " + matchMode.getMatchOperator() + " :queryString";
463
				
464
			} else {
465
				
466
				taxonSubselect = "select t.id from " +
467
					" Taxon t" +
468
					" join t.name n "+
469
					" where" +
470
					" n." + searchField +  " " + matchMode.getMatchOperator() + " :queryString";
471

    
472
				synonymSubselect = "select s.id from" +
473
					" Taxon t" + // the taxa
474
					" join t.synonymRelations sr" +
475
					" join sr.relatedFrom s" + // the synonyms
476
					" join s.name sn"+ 
477
					" where" +
478
					" sn." + searchField +  " " + matchMode.getMatchOperator() + " :queryString";
479
			}
480
			
481
		}
482
		
483
		Query subTaxon = null;
484
		Query subSynonym = null;
485
		if(clazz.equals(Taxon.class)){
486
			// find Taxa
487
			subTaxon = getSession().createQuery(taxonSubselect).setParameter("queryString", hqlQueryString);
488
			//subTaxon = getSession().createQuery(taxonSubselect);
489
			
490
			if(doAreaRestriction){
491
				subTaxon.setParameterList("namedAreasUuids", namedAreasUuids);
492
			}	
493
			if(classification != null){
494
				subTaxon.setParameter("classification", classification);
495
			}
496
		} else if(clazz.equals(Synonym.class)){
497
			// find synonyms
498
			subSynonym = getSession().createQuery(synonymSubselect).setParameter("queryString", hqlQueryString);
499
			
500
			if(doAreaRestriction){
501
				subSynonym.setParameterList("namedAreasUuids", namedAreasUuids);
502
			}		
503
			if(classification != null){
504
				subSynonym.setParameter("classification", classification);
505
			}
506
		} else {
507
			// find taxa and synonyms
508
			subSynonym = getSession().createQuery(synonymSubselect).setParameter("queryString", hqlQueryString);
509
			subTaxon = getSession().createQuery(taxonSubselect).setParameter("queryString", hqlQueryString);
510
			if(doAreaRestriction){
511
				subTaxon.setParameterList("namedAreasUuids", namedAreasUuids);
512
				subSynonym.setParameterList("namedAreasUuids", namedAreasUuids);
513
			}
514
			if(classification != null){
515
				subTaxon.setParameter("classification", classification);
516
				subSynonym.setParameter("classification", classification);
517
			}
518
		}
519
		
520
		List<Integer> taxa = new ArrayList<Integer>();
521
		List<Integer> synonyms = new ArrayList<Integer>();
522
		if(clazz.equals(Taxon.class)){
523
			taxa = subTaxon.list();
524
			
525
		}else if (clazz.equals(Synonym.class)){
526
			synonyms = subSynonym.list();
527
		}else {
528
			taxa = subTaxon.list();
529
			synonyms = subSynonym.list();
530
		}
531
		if(clazz.equals(Taxon.class)){
532
			if  (taxa.size()>0){
533
				if (doNotReturnFullEntities){
534
					hql = "select " + selectWhat + ", 'taxon' from " + clazz.getSimpleName() + " t" + " where t.id in (:taxa)";
535
				}else{
536
					hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t" + " where t.id in (:taxa)";
537
				}
538
			}else{
539
				hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t";
540
			}
541
		} else if(clazz.equals(Synonym.class) ){
542
			if (synonyms.size()>0){
543
				if (doNotReturnFullEntities){
544
					hql = "select " + selectWhat + ", 'synonym' from " + clazz.getSimpleName() + " t" + " where t.id in (:synonyms)";
545
				}else{
546
					hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t" + " where t.id in (:synonyms)";		
547
				}
548
			}else{
549
				hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t";
550
			}
551
		} else {
552
			
553
			if(synonyms.size()>0 && taxa.size()>0){
554
				if (doNotReturnFullEntities &&  !doCount ){
555
					// in doNotReturnFullEntities mode it is nesscary to also return the type of the matching entities:
556
					hql = "select " + selectWhat + ", case when t.id in (:taxa) then 'taxon' else 'synonym' end" + " from " + clazz.getSimpleName() + " t" + " where t.id in (:taxa) OR t.id in (:synonyms)";
557
				}else{
558
					hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t" + " where t.id in (:taxa) OR t.id in (:synonyms)";
559
				}
560
			}else if (synonyms.size()>0 ){
561
				if (doNotReturnFullEntities &&  !doCount ){
562
					// in doNotReturnFullEntities mode it is nesscary to also return the type of the matching entities:
563
					hql = "select " + selectWhat + ", 'synonym' from " + clazz.getSimpleName() + " t" + " where t.id in (:synonyms)";	
564
				} else {
565
					hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t" + " where t.id in (:synonyms)";		
566
				}
567
			} else if (taxa.size()>0 ){
568
				if (doNotReturnFullEntities &&  !doCount ){
569
					// in doNotReturnFullEntities mode it is nesscary to also return the type of the matching entities:
570
					hql = "select " + selectWhat + ", 'taxon' from " + clazz.getSimpleName() + " t" + " where t.id in (:taxa) ";
571
				} else {
572
					hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t" + " where t.id in (:taxa) ";
573
				}
574
			} else{
575
				hql = "select " + selectWhat + " from " + clazz.getSimpleName() + " t";
576
			}
577
		}
578
		
579
		if (hql == "") return null;
580
		if(!doCount){
581
			hql += " order by t.name.genusOrUninomial, case when t.name.specificEpithet like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
582
		}
583
	
584
		Query query = getSession().createQuery(hql);
585
				
586
		if(clazz.equals(Taxon.class) && taxa.size()>0){
587
			//find taxa
588
			query.setParameterList("taxa", taxa );
589
		} else if(clazz.equals(Synonym.class) && synonyms.size()>0){
590
			// find synonyms
591
			query.setParameterList("synonyms", synonyms);
592
			
593
		
594
		} else {
595
			// find taxa and synonyms
596
			if (taxa.size()>0){
597
				query.setParameterList("taxa", taxa);
598
			}
599
			if (synonyms.size()>0){
600
				query.setParameterList("synonyms",synonyms);
601
			}
602
			if (taxa.size()== 0 && synonyms.size() == 0){
603
				return null;
604
			}
605
		}
606
		return query;
607
		
608
	}
609
	
610
	
611
	/**
612
	 * @param clazz
613
	 * @param searchField the field in TaxonNameBase to be searched through usually either <code>nameCache</code> or <code>titleCache</code>
614
	 * @param queryString
615
	 * @param classification TODO
616
	 * @param matchMode
617
	 * @param namedAreas
618
	 * @param pageSize
619
	 * @param pageNumber
620
	 * @param doCount
621
	 * @return
622
	 * 
623
	 * FIXME implement classification restriction & implement test: see {@link TaxonDaoHibernateImplTest#testCountTaxaByName()}
624
	 */
625
	private Query prepareTaxaByName(Class<? extends TaxonBase> clazz, String searchField, String queryString, Classification classification,
626
			MatchMode matchMode, Set<NamedArea> namedAreas, Integer pageSize, Integer pageNumber, boolean doCount) {
627

    
628
		Query query = prepareQuery(clazz, searchField, queryString, classification,	matchMode, namedAreas, doCount, false);
629
		
630
		if(pageSize != null &&  !doCount) {
631
			query.setMaxResults(pageSize);
632
			if(pageNumber != null) {
633
				query.setFirstResult(pageNumber * pageSize);
634
			}
635
		}
636
		
637
		return query;
638
	}
639

    
640
	private Query prepareTaxaByCommonName(String queryString, Classification classification,
641
			MatchMode matchMode, Set<NamedArea> namedAreas, Integer pageSize, Integer pageNumber, boolean doCount){
642
				
643
		String hql= "from Taxon t " +
644
		"join t.descriptions d "+
645
		"join d.descriptionElements e " +
646
		"join e.feature f " +
647
		"where f.supportsCommonTaxonName = true and e.name "+matchMode.getMatchOperator()+" :queryString";//and ls.text like 'common%'";
648
		
649
		Query query = getSession().createQuery(hql);
650
		
651
		query.setParameter("queryString", queryString);
652
		
653
		if(pageSize != null &&  !doCount) {
654
			query.setMaxResults(pageSize);
655
			if(pageNumber != null) {
656
				query.setFirstResult(pageNumber * pageSize);
657
			}
658
		}
659
		return query;
660
	}
661
	
662
	/* (non-Javadoc)
663
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countTaxaByName(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, eu.etaxonomy.cdm.persistence.query.SelectMode, eu.etaxonomy.cdm.model.reference.Reference, java.util.Set)
664
	 */
665
	public long countTaxaByName(Class<? extends TaxonBase> clazz, String queryString, Classification classification,
666
		MatchMode matchMode, Set<NamedArea> namedAreas) {
667
		
668
		boolean doCount = true;
669
		Query query = prepareTaxaByName(clazz, "nameCache", queryString, classification, matchMode, namedAreas, null, null, doCount);
670
		if (query != null) {
671
			return (Long)query.uniqueResult();
672
		}else{
673
			return 0;
674
		}
675
	}
676

    
677
	/**
678
	 * @param namedAreas
679
	 * @param areasExpanded
680
	 * @param areaQuery
681
	 */
682
	private void expandNamedAreas(Collection<NamedArea> namedAreas, Set<NamedArea> areasExpanded, Query areaQuery) {
683
		List<NamedArea> childAreas;
684
		for(NamedArea a : namedAreas){
685
			areasExpanded.add(a);
686
			areaQuery.setParameter("area", a);
687
			childAreas = areaQuery.list();
688
			if(childAreas.size() > 0){
689
				areasExpanded.addAll(childAreas);
690
				expandNamedAreas(childAreas, areasExpanded, areaQuery);
691
			}
692
		}
693
	}
694
	
695
//	/* (non-Javadoc)
696
//	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countTaxaByName(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, eu.etaxonomy.cdm.persistence.query.SelectMode)
697
//	 */
698
//	public Integer countTaxaByName(String queryString, MatchMode matchMode, SelectMode selectMode) {		
699
//		return countTaxaByName(queryString, matchMode, selectMode, null);
700
//	}
701

    
702
//	/* (non-Javadoc)
703
//	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countTaxaByName(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, eu.etaxonomy.cdm.persistence.query.SelectMode, eu.etaxonomy.cdm.model.reference.Reference)
704
//	 */
705
//	public Integer countTaxaByName(String queryString, 
706
//			MatchMode matchMode, SelectMode selectMode, Reference sec) {
707
//
708
//		Long count = countTaxaByName(queryString, matchMode, selectMode, sec, null);
709
//		return count.intValue();
710
//
711
//	}
712
	
713
//	public Integer countTaxaByName(String queryString, MatchMode matchMode, Boolean accepted) {
714
//		
715
//		SelectMode selectMode = (accepted ? SelectMode.TAXA : SelectMode.SYNONYMS);
716
//		Long count = countTaxaByName(queryString, matchMode, selectMode, null, null);
717
//		return count.intValue();
718
//	}
719
	
720
	public List<TaxonBase> getAllTaxonBases(Integer pagesize, Integer page) {
721
		return super.list(pagesize, page);
722
	}
723

    
724
	/*
725
	 * (non-Javadoc)
726
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getAllSynonyms(java.lang.Integer, java.lang.Integer)
727
	 */
728
	public List<Synonym> getAllSynonyms(Integer limit, Integer start) {
729
		Criteria criteria = getSession().createCriteria(Synonym.class);
730
		
731
		if(limit != null) {
732
			criteria.setFirstResult(start);
733
			criteria.setMaxResults(limit);
734
		}
735
		
736
		return criteria.list();
737
	}
738

    
739
	/*
740
	 * (non-Javadoc)
741
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getAllTaxa(java.lang.Integer, java.lang.Integer)
742
	 */
743
	public List<Taxon> getAllTaxa(Integer limit, Integer start) {
744
        Criteria criteria = getSession().createCriteria(Taxon.class);
745
		
746
		if(limit != null) {
747
			criteria.setFirstResult(start);
748
			criteria.setMaxResults(limit);
749
		}
750
		
751
		return criteria.list();
752
	}
753

    
754
	/*
755
	 * (non-Javadoc)
756
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getAllRelationships(java.lang.Integer, java.lang.Integer)
757
	 */
758
	public List<RelationshipBase> getAllRelationships(Integer limit, Integer start) {
759
		AuditEvent auditEvent = getAuditEventFromContext();
760
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
761
		    Criteria criteria = getSession().createCriteria(RelationshipBase.class);
762
		    criteria.setFirstResult(start);
763
		    criteria.setMaxResults(limit);
764
		    return (List<RelationshipBase>)criteria.list();
765
		} else {
766
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(RelationshipBase.class,auditEvent.getRevisionNumber());
767
			return (List<RelationshipBase>)query.getResultList();
768
		}
769
	}
770
	
771
	/** Sets the taxonomic parent to null. Does not handle taxonomic relationships. */
772
//	private boolean nullifyTaxonomicParent(Taxon taxon) {
773
//
774
//		try {
775
//			Method nullifyTaxonomicParent = taxon.getClass().getMethod("nullifyTaxonomicParent");
776
//			nullifyTaxonomicParent.invoke(taxon);
777
//		} catch (NoSuchMethodException ex) {
778
//			logger.error("NoSuchMethod: " + ex.getMessage());
779
//			return false;
780
//		} catch (IllegalArgumentException ex) {
781
//			logger.error("IllegalArgumentException: " + ex.getMessage());
782
//			return false;
783
//		} catch (IllegalAccessException ex) {
784
//			logger.error("IllegalAccessException: " + ex.getMessage());
785
//			return false;
786
//		} catch (InvocationTargetException ex) {
787
//			logger.error("IllegalAccessException: " + ex.getMessage());
788
//			return false;
789
//		}
790
//		return true;
791
//	}
792
	
793
	@Override
794
	public UUID delete(TaxonBase taxonBase) throws DataAccessException{
795
		if (taxonBase == null){
796
			logger.warn("TaxonBase was 'null'");
797
			return null;
798
		}
799
		
800
		// Merge the object in if it is detached
801
		//
802
		// I think this is preferable to catching lazy initialization errors 
803
		// as that solution only swallows and hides the exception, but doesn't 
804
		// actually solve it.
805
		getSession().merge(taxonBase);
806
		
807
		if (taxonBase instanceof Taxon){ //	is Taxon
808
			for (Iterator<TaxonRelationship> iterator = ((Taxon)taxonBase).getRelationsFromThisTaxon().iterator(); iterator.hasNext();){
809
				TaxonRelationship relationFromThisTaxon = iterator.next();
810
				
811
				// decrease children count of taxonomic parent by one
812
				if (relationFromThisTaxon.getType().equals(TaxonRelationshipType.TAXONOMICALLY_INCLUDED_IN())) {
813
					Taxon toTaxon = relationFromThisTaxon.getToTaxon(); // parent
814
					if (toTaxon != null) {
815
						toTaxon.setTaxonomicChildrenCount(toTaxon.getTaxonomicChildrenCount() - 1);	
816
					}
817
				}
818
			}
819
		}
820
		
821
		return super.delete(taxonBase);
822
	}
823

    
824

    
825
	/* (non-Javadoc)
826
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#findByName(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, int, int, boolean)
827
	 */
828
	public List<TaxonBase> findByNameTitleCache(Class<? extends TaxonBase>clazz, String queryString, Classification classification, MatchMode matchMode, Set<NamedArea> namedAreas, Integer pageNumber, Integer pageSize, List<String> propertyPaths) {
829
	
830
		boolean doCount = false;
831
		Query query = prepareTaxaByName(clazz, "titleCache", queryString, classification, matchMode, namedAreas, pageSize, pageNumber, doCount);
832
		if (query != null){
833
			List<TaxonBase> results = query.list();
834
			defaultBeanInitializer.initializeAll(results, propertyPaths);
835
			return results;
836
		}
837
		return new ArrayList<TaxonBase>();
838

    
839
	}
840

    
841
	/*
842
	 * (non-Javadoc)
843
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countMatchesByName(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, boolean)
844
	 */
845
	public int countMatchesByName(String queryString, MatchMode matchMode, boolean onlyAcccepted) {
846
		checkNotInPriorView("TaxonDaoHibernateImpl.countMatchesByName(String queryString, ITitledDao.MATCH_MODE matchMode, boolean onlyAcccepted)");
847
		Criteria crit = getSession().createCriteria(type);
848
		crit.add(Restrictions.ilike("titleCache", matchMode.queryStringFrom(queryString)));
849
		crit.setProjection(Projections.rowCount());
850
		int result = ((Integer)crit.list().get(0)).intValue();
851
		return result;
852
	}
853

    
854
	/*
855
	 * (non-Javadoc)
856
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countMatchesByName(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, boolean, java.util.List)
857
	 */
858
	public int countMatchesByName(String queryString, MatchMode matchMode, boolean onlyAcccepted, List<Criterion> criteria) {
859
		checkNotInPriorView("TaxonDaoHibernateImpl.countMatchesByName(String queryString, ITitledDao.MATCH_MODE matchMode, boolean onlyAcccepted, List<Criterion> criteria)");
860
		Criteria crit = getSession().createCriteria(type);
861
		crit.add(Restrictions.ilike("titleCache", matchMode.queryStringFrom(queryString)));
862
		if(criteria != null){
863
			for (Criterion criterion : criteria) {
864
				crit.add(criterion);
865
			}
866
		}
867
		crit.setProjection(Projections.rowCount());
868
		int result = ((Integer)crit.list().get(0)).intValue();
869
		return result;
870
	}
871

    
872
	/*
873
	 * (non-Javadoc)
874
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countTaxonRelationships(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType, eu.etaxonomy.cdm.model.common.RelationshipBase.Direction)
875
	 */
876
	public int countTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Direction direction) {
877
		AuditEvent auditEvent = getAuditEventFromContext();
878
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
879
		    Query query = null;
880
		
881
		    if(type == null) {
882
			    query = getSession().createQuery("select count(taxonRelationship) from TaxonRelationship taxonRelationship where taxonRelationship."+direction+" = :relatedTaxon");
883
		    } else {
884
			    query = getSession().createQuery("select count(taxonRelationship) from TaxonRelationship taxonRelationship where taxonRelationship."+direction+" = :relatedTaxon and taxonRelationship.type = :type");
885
			    query.setParameter("type",type);
886
		    }
887
		    query.setParameter("relatedTaxon", taxon);
888
		
889
		    return ((Long)query.uniqueResult()).intValue();
890
		} else {
891
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(TaxonRelationship.class,auditEvent.getRevisionNumber());
892
			query.add(AuditEntity.relatedId(direction.toString()).eq(taxon.getId()));
893
			query.addProjection(AuditEntity.id().count("id"));
894
			
895
			if(type != null) {
896
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
897
		    }
898
			
899
			return ((Long)query.getSingleResult()).intValue();
900
		}
901
	}
902

    
903
	/*
904
	 * (non-Javadoc)
905
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countSynonyms(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType)
906
	 */
907
	public int countSynonyms(Taxon taxon, SynonymRelationshipType type) {
908
		AuditEvent auditEvent = getAuditEventFromContext();
909
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
910
			Criteria criteria = getSession().createCriteria(SynonymRelationship.class);
911

    
912
			criteria.add(Restrictions.eq("relatedTo", taxon));
913
		    if(type != null) {
914
		    	criteria.add(Restrictions.eq("type", type));
915
		    } 
916
		    criteria.setProjection(Projections.rowCount());
917
			return (Integer)criteria.uniqueResult();
918
		} else {
919
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(SynonymRelationship.class,auditEvent.getRevisionNumber());
920
			query.add(AuditEntity.relatedId("relatedTo").eq(taxon.getId()));
921
			query.addProjection(AuditEntity.id().count("id"));
922
			
923
			if(type != null) {
924
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
925
		    }
926
			
927
			return ((Long)query.getSingleResult()).intValue();
928
		}
929
	}
930
	
931
	/*
932
	 * (non-Javadoc)
933
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countSynonyms(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType)
934
	 */
935
	public int countSynonyms(Synonym synonym, SynonymRelationshipType type) {
936
		AuditEvent auditEvent = getAuditEventFromContext();
937
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
938
			Criteria criteria = getSession().createCriteria(SynonymRelationship.class);
939

    
940
			criteria.add(Restrictions.eq("relatedFrom", synonym));
941
		    if(type != null) {
942
		    	criteria.add(Restrictions.eq("type", type));
943
		    } 
944
		    
945
		    criteria.setProjection(Projections.rowCount());
946
			return (Integer)criteria.uniqueResult();
947
		} else {
948
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(SynonymRelationship.class,auditEvent.getRevisionNumber());
949
			query.add(AuditEntity.relatedId("relatedFrom").eq(synonym.getId()));
950
			query.addProjection(AuditEntity.id().count("id"));
951
			
952
			if(type != null) {
953
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
954
		    }
955
			
956
			return ((Long)query.getSingleResult()).intValue();
957
		}
958
	}
959

    
960
	/*
961
	 * (non-Javadoc)
962
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countTaxaByName(java.lang.Class, java.lang.String, java.lang.String, java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.name.Rank)
963
	 */
964
	public int countTaxaByName(Class<? extends TaxonBase> clazz, String genusOrUninomial, String infraGenericEpithet, String specificEpithet,	String infraSpecificEpithet, Rank rank) {
965
		checkNotInPriorView("TaxonDaoHibernateImpl.countTaxaByName(Boolean accepted, String genusOrUninomial,	String infraGenericEpithet, String specificEpithet,	String infraSpecificEpithet, Rank rank)");
966
        Criteria criteria = null;
967
		
968
		if(clazz == null) {
969
			criteria = getSession().createCriteria(TaxonBase.class);
970
		} else {
971
			criteria = getSession().createCriteria(clazz);		
972
		}
973
		
974
		criteria.setFetchMode( "name", FetchMode.JOIN );
975
		criteria.createAlias("name", "name");
976
		
977
		if(genusOrUninomial == null) {
978
			criteria.add(Restrictions.isNull("name.genusOrUninomial"));
979
		} else if(!genusOrUninomial.equals("*")) {
980
			criteria.add(Restrictions.eq("name.genusOrUninomial", genusOrUninomial));
981
		}
982
		
983
		if(infraGenericEpithet == null) {
984
			criteria.add(Restrictions.isNull("name.infraGenericEpithet"));
985
		} else if(!infraGenericEpithet.equals("*")) {
986
			criteria.add(Restrictions.eq("name.infraGenericEpithet", infraGenericEpithet));
987
		} 
988
		
989
		if(specificEpithet == null) {
990
			criteria.add(Restrictions.isNull("name.specificEpithet"));
991
		} else if(!specificEpithet.equals("*")) {
992
			criteria.add(Restrictions.eq("name.specificEpithet", specificEpithet));
993
			
994
		}
995
		
996
		if(infraSpecificEpithet == null) {
997
			criteria.add(Restrictions.isNull("name.infraSpecificEpithet"));
998
		} else if(!infraSpecificEpithet.equals("*")) {
999
			criteria.add(Restrictions.eq("name.infraSpecificEpithet", infraSpecificEpithet));
1000
		}
1001

    
1002
		if(rank != null) {
1003
			criteria.add(Restrictions.eq("name.rank", rank));
1004
		}
1005
		
1006
		criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
1007
	
1008
		return (Integer)criteria.uniqueResult();
1009
	}
1010

    
1011
	/*
1012
	 * (non-Javadoc)
1013
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#findTaxaByName(java.lang.Class, java.lang.String, java.lang.String, java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.name.Rank, java.lang.Integer, java.lang.Integer)
1014
	 */
1015
	public List<TaxonBase> findTaxaByName(Class<? extends TaxonBase> clazz, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, Rank rank, Integer pageSize,	Integer pageNumber) {
1016
		checkNotInPriorView("TaxonDaoHibernateImpl.findTaxaByName(Boolean accepted, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, Rank rank, Integer pageSize,	Integer pageNumber)");
1017
		Criteria criteria = null;
1018
		
1019
		if(clazz == null) {
1020
			criteria = getSession().createCriteria(TaxonBase.class);
1021
		} else {
1022
			criteria = getSession().createCriteria(clazz);
1023
		}
1024
		
1025
		criteria.setFetchMode( "name", FetchMode.JOIN );
1026
		criteria.createAlias("name", "name");
1027
		
1028
		if(genusOrUninomial == null) {
1029
			criteria.add(Restrictions.isNull("name.genusOrUninomial"));
1030
		} else if(!genusOrUninomial.equals("*")) {
1031
			criteria.add(Restrictions.eq("name.genusOrUninomial", genusOrUninomial));
1032
		}
1033
		
1034
		if(infraGenericEpithet == null) {
1035
			criteria.add(Restrictions.isNull("name.infraGenericEpithet"));
1036
		} else if(!infraGenericEpithet.equals("*")) {
1037
			criteria.add(Restrictions.eq("name.infraGenericEpithet", infraGenericEpithet));
1038
		} 
1039
		
1040
		if(specificEpithet == null) {
1041
			criteria.add(Restrictions.isNull("name.specificEpithet"));
1042
		} else if(!specificEpithet.equals("*")) {
1043
			criteria.add(Restrictions.eq("name.specificEpithet", specificEpithet));
1044
			
1045
		}
1046
		
1047
		if(infraSpecificEpithet == null) {
1048
			criteria.add(Restrictions.isNull("name.infraSpecificEpithet"));
1049
		} else if(!infraSpecificEpithet.equals("*")) {
1050
			criteria.add(Restrictions.eq("name.infraSpecificEpithet", infraSpecificEpithet));
1051
		}
1052
		
1053
		if(rank != null) {
1054
			criteria.add(Restrictions.eq("name.rank", rank));
1055
		}
1056
		
1057
		if(pageSize != null) {
1058
	    	criteria.setMaxResults(pageSize);
1059
		    if(pageNumber != null) {
1060
		    	criteria.setFirstResult(pageNumber * pageSize);
1061
		    } else {
1062
		    	criteria.setFirstResult(0);
1063
		    }
1064
		}
1065
	
1066
		return (List<TaxonBase>)criteria.list();
1067
	}
1068

    
1069
	/*
1070
	 * (non-Javadoc)
1071
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getTaxonRelationships(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List, eu.etaxonomy.cdm.model.common.RelationshipBase.Direction)
1072
	 */
1073
	public List<TaxonRelationship> getTaxonRelationships(Taxon taxon,	TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1074
		AuditEvent auditEvent = getAuditEventFromContext();
1075
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
1076
			Criteria criteria = getSession().createCriteria(TaxonRelationship.class);
1077
            
1078
			criteria.add(Restrictions.eq("relatedTo", taxon));
1079
		    if(type != null) {
1080
		    	criteria.add(Restrictions.eq("type", type));
1081
		    } 
1082
		
1083
            addOrder(criteria,orderHints);
1084
		
1085
		    if(pageSize != null) {
1086
		    	criteria.setMaxResults(pageSize);
1087
		        if(pageNumber != null) {
1088
		        	criteria.setFirstResult(pageNumber * pageSize);
1089
		        } else {
1090
		        	criteria.setFirstResult(0);
1091
		        }
1092
		    }
1093
		
1094
		    List<TaxonRelationship> result = (List<TaxonRelationship>)criteria.list();
1095
		    defaultBeanInitializer.initializeAll(result, propertyPaths);
1096
		    
1097
		    return result;
1098
		} else {
1099
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(TaxonRelationship.class,auditEvent.getRevisionNumber());
1100
			query.add(AuditEntity.relatedId("relatedTo").eq(taxon.getId()));
1101
			
1102
			if(type != null) {
1103
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
1104
		    }
1105
			
1106
			if(pageSize != null) {
1107
		        query.setMaxResults(pageSize);
1108
		        if(pageNumber != null) {
1109
		            query.setFirstResult(pageNumber * pageSize);
1110
		        } else {
1111
		    	    query.setFirstResult(0);
1112
		        }
1113
		    }
1114
			
1115
			List<TaxonRelationship> result = (List<TaxonRelationship>)query.getResultList();
1116
			defaultBeanInitializer.initializeAll(result, propertyPaths);
1117
			
1118
			// Ugly, but for now, there is no way to sort on a related entity property in Envers,
1119
			// and we can't live without this functionality in CATE as it screws up the whole 
1120
			// taxon tree thing
1121
			if(orderHints != null && !orderHints.isEmpty()) {
1122
			    SortedSet<TaxonRelationship> sortedList = new TreeSet<TaxonRelationship>(new TaxonRelationshipFromTaxonComparator());
1123
			    sortedList.addAll(result);
1124
			    return new ArrayList<TaxonRelationship>(sortedList);
1125
			}
1126
			
1127
			return result;
1128
		}
1129
	}
1130
	
1131
	class TaxonRelationshipFromTaxonComparator implements Comparator<TaxonRelationship> {
1132

    
1133
		public int compare(TaxonRelationship o1, TaxonRelationship o2) {
1134
			return o1.getFromTaxon().getTitleCache().compareTo(o2.getFromTaxon().getTitleCache());
1135
		}
1136
		
1137
	}
1138
	
1139
	class SynonymRelationshipFromTaxonComparator implements Comparator<SynonymRelationship> {
1140

    
1141
		public int compare(SynonymRelationship o1, SynonymRelationship o2) {
1142
			return o1.getSynonym().getTitleCache().compareTo(o2.getSynonym().getTitleCache());
1143
		}
1144
		
1145
	}
1146

    
1147
	/*
1148
	 * (non-Javadoc)
1149
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getSynonyms(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)
1150
	 */
1151
	public List<SynonymRelationship> getSynonyms(Taxon taxon, SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
1152
		AuditEvent auditEvent = getAuditEventFromContext();
1153
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
1154
            Criteria criteria = getSession().createCriteria(SynonymRelationship.class);
1155
            
1156
			criteria.add(Restrictions.eq("relatedTo", taxon));
1157
		    if(type != null) {
1158
		    	criteria.add(Restrictions.eq("type", type));
1159
		    } 
1160
		
1161
            addOrder(criteria,orderHints);
1162
		
1163
		    if(pageSize != null) {
1164
		    	criteria.setMaxResults(pageSize);
1165
		        if(pageNumber != null) {
1166
		        	criteria.setFirstResult(pageNumber * pageSize);
1167
		        } else {
1168
		        	criteria.setFirstResult(0);
1169
		        }
1170
		    }
1171
		
1172
		    List<SynonymRelationship> result = (List<SynonymRelationship>)criteria.list();
1173
		    defaultBeanInitializer.initializeAll(result, propertyPaths);
1174
		    
1175
		    return result;
1176
		} else {
1177
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(SynonymRelationship.class,auditEvent.getRevisionNumber());
1178
			query.add(AuditEntity.relatedId("relatedTo").eq(taxon.getId()));
1179
			
1180
			if(type != null) {
1181
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
1182
		    }
1183
			
1184
			if(pageSize != null) {
1185
		        query.setMaxResults(pageSize);
1186
		        if(pageNumber != null) {
1187
		            query.setFirstResult(pageNumber * pageSize);
1188
		        } else {
1189
		    	    query.setFirstResult(0);
1190
		        }
1191
		    }
1192
			
1193
			List<SynonymRelationship> result = (List<SynonymRelationship>)query.getResultList();
1194
			defaultBeanInitializer.initializeAll(result, propertyPaths);
1195
			
1196
			return result;
1197
		}
1198
	}
1199
	
1200
	/*
1201
	 * (non-Javadoc)
1202
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getSynonyms(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)
1203
	 */
1204
	public List<SynonymRelationship> getSynonyms(Synonym synonym, SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
1205
		AuditEvent auditEvent = getAuditEventFromContext();
1206
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
1207
            Criteria criteria = getSession().createCriteria(SynonymRelationship.class);
1208
            
1209
			criteria.add(Restrictions.eq("relatedFrom", synonym));
1210
		    if(type != null) {
1211
		    	criteria.add(Restrictions.eq("type", type));
1212
		    } 
1213
		
1214
            addOrder(criteria,orderHints);
1215
		
1216
		    if(pageSize != null) {
1217
		    	criteria.setMaxResults(pageSize);
1218
		        if(pageNumber != null) {
1219
		        	criteria.setFirstResult(pageNumber * pageSize);
1220
		        } else {
1221
		        	criteria.setFirstResult(0);
1222
		        }
1223
		    }
1224
		
1225
		    List<SynonymRelationship> result = (List<SynonymRelationship>)criteria.list();
1226
		    defaultBeanInitializer.initializeAll(result, propertyPaths);
1227
		    
1228
		    return result;
1229
		} else {
1230
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(SynonymRelationship.class,auditEvent.getRevisionNumber());
1231
			query.add(AuditEntity.relatedId("relatedFrom").eq(synonym.getId()));
1232
			
1233
			if(type != null) {
1234
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
1235
		    }
1236
			
1237
			if(pageSize != null) {
1238
		        query.setMaxResults(pageSize);
1239
		        if(pageNumber != null) {
1240
		            query.setFirstResult(pageNumber * pageSize);
1241
		        } else {
1242
		    	    query.setFirstResult(0);
1243
		        }
1244
		    }
1245
			
1246
			List<SynonymRelationship> result = (List<SynonymRelationship>)query.getResultList();
1247
			defaultBeanInitializer.initializeAll(result, propertyPaths);
1248
			
1249
			return result;
1250
		}
1251
	}
1252
	
1253
	@Override
1254
	public void rebuildIndex() {
1255
		FullTextSession fullTextSession = Search.getFullTextSession(getSession());
1256
		
1257
		for(TaxonBase taxonBase : list(null,null)) { // re-index all taxon base
1258
			Hibernate.initialize(taxonBase.getName());
1259
			fullTextSession.index(taxonBase);
1260
		}
1261
		fullTextSession.flushToIndexes();
1262
	}
1263
	
1264
	@Override
1265
	public String suggestQuery(String queryString) {
1266
		checkNotInPriorView("TaxonDaoHibernateImpl.suggestQuery(String queryString)");
1267
		String alternativeQueryString = null;
1268
		if (alternativeSpellingSuggestionParser != null) {
1269
			try {
1270

    
1271
				alternativeSpellingSuggestionParser.parse(queryString);
1272
				org.apache.lucene.search.Query alternativeQuery = alternativeSpellingSuggestionParser.suggest(queryString);
1273
				if (alternativeQuery != null) {
1274
					alternativeQueryString = alternativeQuery
1275
							.toString("name.titleCache");
1276
				}
1277

    
1278
			} catch (ParseException e) {
1279
				throw new QueryParseException(e, queryString);
1280
			}
1281
		}
1282
		return alternativeQueryString;
1283
	}
1284
	
1285
	/*
1286
	 * (non-Javadoc)
1287
	 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheOfAcceptedTaxa(eu.etaxonomy.cdm.model.taxon.Classification)
1288
	 */
1289
	public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification) {
1290

    
1291
		int classificationId = classification.getId();
1292
		
1293
		String queryString = "SELECT nodes.uuid, taxa.titleCache FROM TaxonNode AS nodes LEFT JOIN TaxonBase AS taxa ON nodes.taxon_id = taxa.id WHERE taxa.DTYPE = 'Taxon' AND nodes.classification_id = " + classificationId;
1294
		
1295
		List<Object[]> result = getSession().createSQLQuery(queryString).list();
1296
				
1297
		if(result.size() == 0){
1298
			return null;
1299
		}else{
1300
			List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<UuidAndTitleCache<TaxonNode>>(result.size()); 
1301
			
1302
			for (Object object : result){
1303
				
1304
				Object[] objectArray = (Object[]) object;
1305
				
1306
				UUID uuid = UUID.fromString((String) objectArray[0]);
1307
				String titleCache = (String) objectArray[1];
1308
				
1309
				list.add(new UuidAndTitleCache(TaxonNode.class, uuid, titleCache));
1310
			}
1311
			
1312
			return list;	
1313
		}
1314
	}
1315
	
1316
	
1317
	public class UuidAndTitleCacheOfAcceptedTaxon{
1318
		UUID uuid;
1319
		
1320
		String titleCache;
1321

    
1322
		public UuidAndTitleCacheOfAcceptedTaxon(UUID uuid, String titleCache){
1323
			this.uuid = uuid;
1324
			this.titleCache = titleCache;
1325
		}
1326
		
1327
		public UUID getUuid() {
1328
			return uuid;
1329
		}
1330

    
1331
		public void setUuid(UUID uuid) {
1332
			this.uuid = uuid;
1333
		}
1334

    
1335
		public String getTitleCache() {
1336
			return titleCache;
1337
		}
1338

    
1339
		public void setTitleCache(String titleCache) {
1340
			this.titleCache = titleCache;
1341
		}
1342
	}
1343
	
1344
	@Override
1345
	public TaxonBase find(LSID lsid) {
1346
		TaxonBase taxonBase = super.find(lsid);
1347
		if(taxonBase != null) {
1348
			List<String> propertyPaths = new ArrayList<String>();
1349
			propertyPaths.add("createdBy");
1350
			propertyPaths.add("updatedBy");
1351
			propertyPaths.add("name");
1352
			propertyPaths.add("sec");
1353
			propertyPaths.add("relationsToThisTaxon");
1354
			propertyPaths.add("relationsToThisTaxon.fromTaxon");
1355
			propertyPaths.add("relationsToThisTaxon.toTaxon");
1356
			propertyPaths.add("relationsFromThisTaxon");
1357
			propertyPaths.add("relationsFromThisTaxon.toTaxon");			
1358
			propertyPaths.add("relationsToThisTaxon.type");
1359
			propertyPaths.add("synonymRelations");
1360
			propertyPaths.add("synonymRelations.synonym");
1361
			propertyPaths.add("synonymRelations.type");
1362
			propertyPaths.add("descriptions");
1363
			
1364
			defaultBeanInitializer.initialize(taxonBase, propertyPaths);
1365
		}
1366
		return taxonBase;
1367
	}
1368

    
1369
	public List<TaxonBase> getTaxaByCommonName(String queryString,
1370
			Classification classification, MatchMode matchMode,
1371
			Set<NamedArea> namedAreas, Integer pageSize, Integer pageNumber) {
1372
		// TODO Auto-generated method stub
1373
		return null;
1374
	}
1375
	
1376
	
1377
	public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree){
1378
		List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
1379
		
1380
		inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF()));
1381
		inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF()));
1382
		inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF()));
1383
		
1384
		return inferredSynonyms;
1385
	}
1386
	
1387

    
1388
	/**
1389
	 * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
1390
	 * Very likely only useful for createInferredSynonyms().
1391
	 * @param uuid
1392
	 * @param zooHashMap
1393
	 * @return
1394
	 */
1395
	private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
1396
		ZoologicalName taxonName = this.taxonNameDao.findZoologicalNameByUUID(uuid);
1397
		if (taxonName == null) {
1398
			taxonName = zooHashMap.get(uuid);
1399
		}
1400
		return taxonName;
1401
	}
1402

    
1403
	public List<Synonym> createInferredSynonyms(Taxon taxon, Classification tree, SynonymRelationshipType type){
1404
		List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
1405
		List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
1406

    
1407
		HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
1408
		UUID uuid;
1409
		
1410
		uuid= taxon.getName().getUuid();
1411
		ZoologicalName taxonName = getZoologicalName(uuid, zooHashMap);
1412
		String epithetOfTaxon = taxonName.getSpecificEpithet();
1413
		String genusOfTaxon = taxonName.getGenusOrUninomial();
1414
		Set<TaxonNode> nodes = taxon.getTaxonNodes();
1415
	 	List<String> taxonNames = new ArrayList<String>();
1416
	 	
1417
		for (TaxonNode node: nodes){
1418
			HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
1419
			List<String> synonymsEpithet = new ArrayList<String>();
1420
			
1421
			if (node.getClassification().equals(tree)){
1422
				if (!node.isTopmostNode()){
1423
				TaxonNode parent = (TaxonNode)node.getParent();
1424
				parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
1425
				TaxonNameBase parentName = parent.getTaxon().getName();
1426
				parentName = (TaxonNameBase)HibernateProxyHelper.deproxy(parentName);
1427
				
1428
				//create inferred synonyms for species, subspecies or subgenus
1429
				if (parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())){
1430
					
1431
					Synonym inferredEpithet;
1432
					Synonym inferredGenus = null;
1433
					Synonym potentialCombination;
1434
					
1435
					List<String> propertyPaths = new ArrayList<String>();
1436
					propertyPaths.add("synonym");
1437
					propertyPaths.add("synonym.name");
1438
					List<OrderHint> orderHints = new ArrayList<OrderHint>();
1439
					orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
1440
				
1441
					List<SynonymRelationship> synonymRelationshipsOfGenus = getSynonyms(parent.getTaxon(), SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
1442
					List<SynonymRelationship> synonymRelationshipsOfTaxon= getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);									
1443
					
1444
					if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
1445
												
1446
						for (SynonymRelationship synonymRelationOfGenus:synonymRelationshipsOfGenus){
1447
							TaxonNameBase synName;
1448
							ZoologicalName inferredSynName;
1449
							Synonym syn = synonymRelationOfGenus.getSynonym();
1450
							HibernateProxyHelper.deproxy(syn);
1451
							
1452
							// Determine the idInSource
1453
							String idInSource = getIdInSource(syn);
1454
							
1455
							// Determine the sourceReference
1456
							Reference sourceReference = syn.getSec();
1457
							
1458
							synName = syn.getName();
1459
							ZoologicalName zooName = getZoologicalName(synName.getUuid(), zooHashMap);
1460
							String synGenusName = zooName.getGenusOrUninomial();
1461
							if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
1462
								synonymsGenus.put(synGenusName, idInSource);
1463
							}
1464
							inferredSynName = ZoologicalName.NewInstance(Rank.SPECIES());
1465
							
1466
							// DEBUG
1467
							if (epithetOfTaxon == null) {
1468
								logger.error("This specificEpithet is NULL");
1469
							}
1470
							
1471
							inferredSynName.setSpecificEpithet(epithetOfTaxon);
1472
							inferredSynName.setGenusOrUninomial(synGenusName);
1473
							inferredEpithet = Synonym.NewInstance(inferredSynName, null);
1474
							
1475
							// Set the sourceReference
1476
							inferredEpithet.setSec(sourceReference);
1477

    
1478
							// Add the original source
1479
							if (idInSource != null) {
1480
								IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
1481
								
1482
								// Add the citation
1483
								Reference citation = getCitation(syn);
1484
								if (citation != null) {
1485
									originalSource.setCitation(citation);
1486
									inferredEpithet.addSource(originalSource);
1487
								}
1488
							}
1489
							
1490
							taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_GENUS_OF());
1491
							inferredSynonyms.add(inferredEpithet);
1492
							inferredSynName.generateTitle();
1493
							zooHashMap.put(inferredSynName.getUuid(), inferredSynName);
1494
							taxonNames.add(inferredSynName.getNameCache());
1495
						}
1496
						
1497
						if (!taxonNames.isEmpty()){
1498
						List<String> synNotInCDM = this.taxaByNameNotInDB(taxonNames);
1499
						ZoologicalName name;
1500
						if (!synNotInCDM.isEmpty()){
1501
							inferredSynonymsToBeRemoved.clear();
1502
							
1503
							for (Synonym syn :inferredSynonyms){
1504
								name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1505
								if (!synNotInCDM.contains(name.getNameCache())){
1506
									inferredSynonymsToBeRemoved.add(syn);
1507
								}
1508
							}
1509
							
1510
							// Remove identified Synonyms from inferredSynonyms
1511
							for (Synonym synonym : inferredSynonymsToBeRemoved) {
1512
								inferredSynonyms.remove(synonym);
1513
							}
1514
						}
1515
						}
1516
						
1517
					}else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
1518
						
1519
						
1520
						for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
1521
							TaxonNameBase synName;
1522
							ZoologicalName inferredSynName;
1523
							
1524
							Synonym syn = synonymRelationOfTaxon.getSynonym();
1525
							synName =syn.getName();
1526
							HibernateProxyHelper.deproxy(syn);
1527
							
1528
							// Determine the idInSource
1529
							String idInSource = getIdInSource(syn);
1530
							
1531
							// Determine the sourceReference
1532
							Reference sourceReference = syn.getSec();
1533

    
1534
							synName = syn.getName();
1535
							ZoologicalName zooName = getZoologicalName(synName.getUuid(), zooHashMap);
1536
							String speciesEpithetName = zooName.getSpecificEpithet();
1537
							if (synonymsEpithet != null && !synonymsEpithet.contains(speciesEpithetName)){
1538
								synonymsEpithet.add(speciesEpithetName);
1539
							}
1540
							inferredSynName = ZoologicalName.NewInstance(Rank.SPECIES());
1541
							inferredSynName.setSpecificEpithet(speciesEpithetName);
1542
							inferredSynName.setGenusOrUninomial(genusOfTaxon);
1543
							inferredGenus = Synonym.NewInstance(inferredSynName, null);
1544
							
1545
							// Set the sourceReference
1546
							inferredGenus.setSec(sourceReference);
1547
							
1548
							// Add the original source
1549
							if (idInSource != null) {
1550
								IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredGenusOf", syn.getSec(), null);
1551
								
1552
								// Add the citation
1553
								Reference citation = getCitation(syn);
1554
								if (citation != null) {
1555
									originalSource.setCitation(citation);
1556
									inferredGenus.addSource(originalSource);
1557
								}
1558
							}
1559

    
1560
							taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_EPITHET_OF());
1561
							inferredSynonyms.add(inferredGenus);
1562
							inferredSynName.generateTitle();
1563
							zooHashMap.put(inferredSynName.getUuid(), inferredSynName);
1564
							taxonNames.add(inferredSynName.getNameCache());
1565
						}
1566
						
1567
						if (!taxonNames.isEmpty()){
1568
							List<String> synNotInCDM = this.taxaByNameNotInDB(taxonNames);
1569
							ZoologicalName name;
1570
							if (!synNotInCDM.isEmpty()){
1571
								inferredSynonymsToBeRemoved.clear();
1572
								
1573
								for (Synonym syn :inferredSynonyms){
1574
									name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1575
									if (!synNotInCDM.contains(name.getNameCache())){
1576
										inferredSynonymsToBeRemoved.add(syn);
1577
									}
1578
								}
1579
								
1580
								// Remove identified Synonyms from inferredSynonyms
1581
								for (Synonym synonym : inferredSynonymsToBeRemoved) {
1582
									inferredSynonyms.remove(synonym);
1583
								}
1584
							}
1585
						}
1586
						
1587
					}else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
1588
						
1589
						Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
1590
						
1591
						for (SynonymRelationship synonymRelationOfGenus:synonymRelationshipsOfGenus){
1592
							TaxonNameBase synName;
1593
							Synonym syn = synonymRelationOfGenus.getSynonym();
1594
							synName =syn.getName();
1595
							
1596
							HibernateProxyHelper.deproxy(syn);
1597
							
1598
							// Set the sourceReference
1599
							sourceReference = syn.getSec();
1600

    
1601
							// Determine the idInSource
1602
							String idInSource = getIdInSource(syn);
1603

    
1604
							ZoologicalName zooName = getZoologicalName(synName.getUuid(), zooHashMap);
1605
							String synGenusName = zooName.getGenusOrUninomial();
1606
							if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
1607
								synonymsGenus.put(synGenusName, idInSource);
1608
							}
1609
						}
1610
						
1611
						ZoologicalName inferredSynName;
1612
						for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
1613
							
1614
							Synonym syn = synonymRelationOfTaxon.getSynonym();
1615
							HibernateProxyHelper.deproxy(syn);
1616
							
1617
							// Set sourceReference
1618
							sourceReference = syn.getSec();
1619
							
1620
							ZoologicalName zooName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1621
							String epithetName = zooName.getSpecificEpithet();
1622
							if (epithetName != null && !synonymsEpithet.contains(epithetName)){
1623
								synonymsEpithet.add(epithetName);
1624
							}
1625
						}
1626
						for (String epithetName:synonymsEpithet){
1627
							for (String genusName: synonymsGenus.keySet()){
1628
								inferredSynName = ZoologicalName.NewInstance(Rank.SPECIES());
1629
								inferredSynName.setSpecificEpithet(epithetName);
1630
								inferredSynName.setGenusOrUninomial(genusName);
1631
								potentialCombination = Synonym.NewInstance(inferredSynName, null);
1632
								
1633
								// Set the sourceReference
1634
								potentialCombination.setSec(sourceReference);
1635
								
1636
								// Add the original source
1637
								String idInSource = synonymsGenus.get(genusName);
1638
								if (idInSource != null) {
1639
									IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "PotentialCombinationOf", sourceReference, null);
1640
									
1641
									// Add the citation
1642
									if (sourceReference != null) {
1643
										originalSource.setCitation(sourceReference);
1644
										potentialCombination.addSource(originalSource);
1645
									}
1646
								}
1647

    
1648
								inferredSynonyms.add(potentialCombination);
1649
								inferredSynName.generateTitle();
1650
								zooHashMap.put(inferredSynName.getUuid(), inferredSynName);
1651
								taxonNames.add(inferredSynName.getNameCache());
1652
							}
1653
							
1654
							if (!taxonNames.isEmpty()){
1655
								List<String> synNotInCDM = this.taxaByNameNotInDB(taxonNames);
1656
								ZoologicalName name;
1657
								if (!synNotInCDM.isEmpty()){
1658
									inferredSynonymsToBeRemoved.clear();
1659
									
1660
									for (Synonym syn :inferredSynonyms){
1661
										try{
1662
											name = (ZoologicalName) syn.getName();
1663
										}catch (ClassCastException e){
1664
											name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1665
										}
1666
										if (!synNotInCDM.contains(name.getNameCache())){
1667
											inferredSynonymsToBeRemoved.add(syn);
1668
										}
1669
									}
1670
									
1671
									// Remove identified Synonyms from inferredSynonyms
1672
									for (Synonym synonym : inferredSynonymsToBeRemoved) {
1673
										inferredSynonyms.remove(synonym);
1674
									}
1675
								}
1676
							}
1677
						}
1678
					}else {
1679
						logger.info("The synonymrelationship type is not defined.");
1680
						return null;
1681
					}
1682
				}
1683
			}
1684
			}
1685
			}
1686
			
1687
		
1688
		return inferredSynonyms;
1689
	}
1690

    
1691

    
1692
	/**
1693
	 * Returns the idInSource for a given Synonym.
1694
	 * @param syn
1695
	 */
1696
	private String getIdInSource(Synonym syn) {
1697
		String idInSource = null;
1698
		Set<IdentifiableSource> sources = syn.getSources();
1699
		if (sources.size() == 1) {
1700
			IdentifiableSource source = sources.iterator().next();
1701
			if (source != null) {
1702
				idInSource  = source.getIdInSource();
1703
			}
1704
		} else if (sources.size() > 1) {
1705
			int count = 1;
1706
			idInSource = "";
1707
			for (IdentifiableSource source : sources) {
1708
				idInSource += source.getIdInSource();
1709
				if (count < sources.size()) {
1710
					idInSource += "; ";
1711
				}
1712
				count++;
1713
			}
1714
		}
1715
		
1716
		return idInSource;
1717
	}
1718
	
1719
	/**
1720
	 * Returns the citation for a given Synonym.
1721
	 * @param syn
1722
	 */
1723
	private Reference getCitation(Synonym syn) {
1724
		Reference citation = null;
1725
		Set<IdentifiableSource> sources = syn.getSources();
1726
		if (sources.size() == 1) {
1727
			IdentifiableSource source = sources.iterator().next();
1728
			if (source != null) {
1729
				citation = source.getCitation();
1730
			}
1731
		} else if (sources.size() > 1) {
1732
			logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
1733
		}
1734
		
1735
		return citation;
1736
	}
1737
	
1738
/*	private void xxx(List<SynonymRelationship> synonymRelationships, HashMap <UUID, ZoologicalName> zooHashMap, SynonymRelationshipType type, String addString){
1739
		
1740
		for (SynonymRelationship synonymRelation:synonymRelationships){
1741
			TaxonNameBase synName;
1742
			NonViralName inferredSynName;
1743
			Synonym syn = synonymRelation.getSynonym();
1744
			HibernateProxyHelper.deproxy(syn);
1745
			
1746
			synName = syn.getName();
1747
			ZoologicalName zooName = zooHashMap.get(synName.getUuid());
1748
			String synGenusName = zooName.getGenusOrUninomial();
1749
			
1750
			switch(type.getId()){
1751
			case SynonymRelationshipType.INFERRED_EPITHET_OF().getId():
1752
				inferredSynName.setSpecificEpithet(addString);
1753
				break;
1754
			case SynonymRelationshipType.INFERRED_GENUS_OF().getId():
1755
				break;
1756
			case SynonymRelationshipType.POTENTIAL_COMBINATION_OF().getId():
1757
				break;
1758
			default:
1759
			}
1760
			if (!synonymsGenus.contains(synGenusName)){
1761
				synonymsGenus.add(synGenusName);
1762
			}
1763
			inferredSynName = NonViralName.NewInstance(Rank.SPECIES());
1764
			inferredSynName.setSpecificEpithet(epithetOfTaxon);
1765
			inferredSynName.setGenusOrUninomial(synGenusName);
1766
			inferredEpithet = Synonym.NewInstance(inferredSynName, null);
1767
			taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_GENUS_OF());
1768
			inferredSynonyms.add(inferredEpithet);
1769
			inferredSynName.generateTitle();
1770
			taxonNames.add(inferredSynName.getNameCache());
1771
		}
1772
			
1773
		
1774
		if (!taxonNames.isEmpty()){
1775
		List<String> synNotInCDM = this.taxaByNameNotInDB(taxonNames);
1776
		ZoologicalName name;
1777
		if (!synNotInCDM.isEmpty()){
1778
			for (Synonym syn :inferredSynonyms){
1779
				name =zooHashMap.get(syn.getName().getUuid());
1780
				if (!synNotInCDM.contains(name.getNameCache())){
1781
					inferredSynonyms.remove(syn);
1782
				}
1783
			}
1784
		}
1785
		}
1786
	}*/
1787

    
1788
	/*
1789
	 * (non-Javadoc)
1790
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#countAllRelationships()
1791
	 */
1792
	public int countAllRelationships() {
1793
		List<RelationshipBase> relationships = this.getAllRelationships(null, 0);
1794
		return relationships.size();
1795
	}
1796
	
1797
	
1798
	public List<String> taxaByNameNotInDB(List<String> taxonNames){
1799
		List<TaxonBase> notInDB = new ArrayList<TaxonBase>();
1800
		//get all taxa, already in db
1801
		Query query = getSession().createQuery("from TaxonNameBase t where t.nameCache IN (:taxonList)");
1802
		query.setParameterList("taxonList", taxonNames);
1803
		List<TaxonNameBase> taxaInDB = query.list();
1804
		//compare the original list with the result of the query
1805
		for (TaxonNameBase taxonName: taxaInDB){
1806
			if (taxonName.isInstanceOf(NonViralName.class)) {
1807
				NonViralName nonViralName = CdmBase.deproxy(taxonName, NonViralName.class);
1808
				String nameCache = nonViralName.getNameCache();
1809
				if (taxonNames.contains(nameCache)){
1810
					taxonNames.remove(nameCache);
1811
				}
1812
			}
1813
		}
1814
				
1815
		return taxonNames;
1816
	}
1817

    
1818
	//TODO: mal nur mit UUID probieren (ohne fetch all properties), vielleicht geht das schneller?
1819
	public List<UUID> findIdenticalTaxonNameIds(List<String> propertyPaths){
1820
		Query query=getSession().createQuery("select tmb2 from ZoologicalName tmb, ZoologicalName tmb2 fetch all properties where tmb.id != tmb2.id and tmb.nameCache = tmb2.nameCache");
1821
		List<UUID> zooNames = query.list();
1822
								
1823
		return zooNames;
1824
		
1825
	}
1826
	
1827
	public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPaths) {
1828
		
1829
		Query query=getSession().createQuery("select tmb2 from ZoologicalName tmb, ZoologicalName tmb2 fetch all properties where tmb.id != tmb2.id and tmb.nameCache = tmb2.nameCache");
1830
		
1831
		List<TaxonNameBase> zooNames = query.list();
1832
		
1833
		TaxonNameComparator taxComp = new TaxonNameComparator();
1834
		Collections.sort(zooNames, taxComp);
1835
		
1836
		for (TaxonNameBase taxonNameBase: zooNames){
1837
			defaultBeanInitializer.initialize(taxonNameBase, propertyPaths);
1838
		}
1839
		
1840
		return zooNames;
1841
	}
1842
	
1843
	public List<TaxonNameBase> findIdenticalNamesNew(List<String> propertyPaths){
1844
		
1845
		//Hole die beiden Source_ids von "Fauna Europaea" und "Erms" und in sources der names darf jeweils nur das entgegengesetzte auftreten (i member of tmb.taxonBases)
1846
		Query query = getSession().createQuery("Select id from Reference where titleCache like 'Fauna Europaea database'");
1847
		List<String> secRefFauna = query.list();
1848
		query = getSession().createQuery("Select id from Reference where titleCache like 'ERMS'");
1849
		List<String> secRefErms = query.list();
1850
		//Query query = getSession().createQuery("select tmb2.nameCache from ZoologicalName tmb, TaxonBase tb1, ZoologicalName tmb2, TaxonBase tb2 where tmb.id != tmb2.id and tb1.name = tmb and tb2.name = tmb2 and tmb.nameCache = tmb2.nameCache and tb1.sec != tb2.sec");
1851
		//Get all names of fauna europaea
1852
		query = getSession().createQuery("select zn.nameCache from ZoologicalName zn, TaxonBase tb where tb.name = zn and tb.sec.id = :secRefFauna");
1853
		query.setParameter("secRefFauna", secRefFauna.get(0));
1854
		List<String> namesFauna= query.list();
1855
		
1856
		//Get all names of erms
1857
		
1858
		query = getSession().createQuery("select zn.nameCache from ZoologicalName zn, TaxonBase tb where tb.name = zn and tb.sec.id = :secRefErms");
1859
		query.setParameter("secRefErms", secRefErms.get(0));
1860
		
1861
		List<String> namesErms = query.list();
1862
		/*TaxonNameComparator comp = new TaxonNameComparator();
1863
		Collections.sort(namesFauna);
1864
		Collections.sort(namesErms);
1865
		*/
1866
		List <String> identicalNames = new ArrayList<String>();
1867
		String predecessor = "";
1868
		
1869
		for (String nameFauna: namesFauna){
1870
			if (namesErms.contains(nameFauna)){
1871
				identicalNames.add(nameFauna);
1872
			}
1873
		}
1874
		
1875
		
1876
		query = getSession().createQuery("from ZoologicalName zn where zn.nameCache IN (:identicalNames)");
1877
		query.setParameterList("identicalNames", identicalNames);
1878
		List<TaxonNameBase> result = query.list();
1879
		TaxonNameBase temp = result.get(0);
1880
		
1881
		Iterator<OriginalSourceBase> sources = temp.getSources().iterator();
1882
				
1883
		TaxonNameComparator taxComp = new TaxonNameComparator();
1884
		Collections.sort(result, taxComp);
1885
		defaultBeanInitializer.initializeAll(result, propertyPaths);
1886
		return result;
1887
		
1888
		}
1889
	
1890
	
1891
	
1892
	public String getPhylumName(TaxonNameBase name){
1893
		List results = new ArrayList();
1894
		try{
1895
		Query query = getSession().createSQLQuery("select getPhylum("+ name.getId()+");");
1896
		results = query.list();
1897
		}catch(Exception e){
1898
			System.err.println(name.getUuid());
1899
			return null;
1900
		}
1901
		System.err.println("phylum of "+ name.getTitleCache() );
1902
		return (String)results.get(0);
1903
	}
1904

    
1905

    
1906
	public long countTaxaByCommonName(String searchString,
1907
			Classification classification, MatchMode matchMode,
1908
			Set<NamedArea> namedAreas) {
1909
		boolean doCount = true;
1910
		Query query = prepareTaxaByCommonName(searchString, classification, matchMode, namedAreas, null, null, doCount);
1911
		if (query != null && !query.list().isEmpty()) {
1912
			Object o = query.uniqueResult();
1913
			if(o != null) {
1914
				return (Long)o;
1915
			}
1916
		}
1917
		return 0;
1918
	}
1919

    
1920

    
1921
	/* (non-Javadoc)
1922
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1923
	 */
1924
	public long deleteSynonymRelationships(Synonym synonym, Taxon taxon) {
1925
		
1926
		String hql = "delete SynonymRelationship sr where sr.relatedFrom = :syn ";
1927
		if (taxon != null){
1928
			hql += " and sr.relatedTo = :taxon";
1929
		}
1930
		Session session = this.getSession();
1931
		Query q = session.createQuery(hql);
1932
		
1933
		q.setParameter("syn", synonym);
1934
		if (taxon != null){
1935
			q.setParameter("taxon", taxon);
1936
		}
1937
		return q.executeUpdate();
1938
	}
1939

    
1940

    
1941
	@Override
1942
	public Integer countSynonymRelationships(TaxonBase taxonBase,
1943
			SynonymRelationshipType type, Direction relatedfrom) {
1944
		AuditEvent auditEvent = getAuditEventFromContext();
1945
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
1946
		    Query query = null;
1947
		
1948
		    if(type == null) {
1949
			    query = getSession().createQuery("select count(synonymRelationship) from SynonymRelationship synonymRelationship where synonymRelationship."+relatedfrom+" = :relatedSynonym");
1950
		    } else {
1951
			    query = getSession().createQuery("select count(synonymRelationship) from SynonymRelationship synonymRelationship where synonymRelationship."+relatedfrom+" = :relatedSynonym and synonymRelationship.type = :type");
1952
			    query.setParameter("type",type);
1953
		    }
1954
		    query.setParameter("relatedTaxon", taxonBase);
1955
		
1956
		    return ((Long)query.uniqueResult()).intValue();
1957
		} else {
1958
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(TaxonRelationship.class,auditEvent.getRevisionNumber());
1959
			query.add(AuditEntity.relatedId(relatedfrom.toString()).eq(taxonBase.getId()));
1960
			query.addProjection(AuditEntity.id().count("id"));
1961
			
1962
			if(type != null) {
1963
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
1964
		    }
1965
			
1966
			return ((Long)query.getSingleResult()).intValue();
1967
		}
1968
	}
1969

    
1970

    
1971
	@Override
1972
	public List<SynonymRelationship> getSynonymRelationships(TaxonBase taxonBase,
1973
			SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1974
			List<OrderHint> orderHints, List<String> propertyPaths,
1975
			Direction direction) {
1976
		
1977
		AuditEvent auditEvent = getAuditEventFromContext();
1978
		if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
1979
			Criteria criteria = getSession().createCriteria(SynonymRelationship.class);
1980
            
1981
			if (direction.equals(Direction.relatedTo)){
1982
				criteria.add(Restrictions.eq("relatedTo", taxonBase));
1983
			}else{
1984
				criteria.add(Restrictions.eq("relatedFrom", taxonBase));
1985
			}
1986
		    if(type != null) {
1987
		    	criteria.add(Restrictions.eq("type", type));
1988
		    } 
1989
		
1990
            addOrder(criteria,orderHints);
1991
		
1992
		    if(pageSize != null) {
1993
		    	criteria.setMaxResults(pageSize);
1994
		        if(pageNumber != null) {
1995
		        	criteria.setFirstResult(pageNumber * pageSize);
1996
		        } else {
1997
		        	criteria.setFirstResult(0);
1998
		        }
1999
		    }
2000
		
2001
		    List<SynonymRelationship> result = (List<SynonymRelationship>)criteria.list();
2002
		    defaultBeanInitializer.initializeAll(result, propertyPaths);
2003
		    
2004
		    return result;
2005
		} else {
2006
			AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(TaxonRelationship.class,auditEvent.getRevisionNumber());
2007
			
2008
			if (direction.equals(Direction.relatedTo)){
2009
				query.add(AuditEntity.relatedId("relatedTo").eq(taxonBase.getId()));
2010
			}else{
2011
				query.add(AuditEntity.relatedId("relatedFrom").eq(taxonBase.getId()));
2012
			}
2013
			
2014
			if(type != null) {
2015
				query.add(AuditEntity.relatedId("type").eq(type.getId()));
2016
		    }
2017
			
2018
			if(pageSize != null) {
2019
		        query.setMaxResults(pageSize);
2020
		        if(pageNumber != null) {
2021
		            query.setFirstResult(pageNumber * pageSize);
2022
		        } else {
2023
		    	    query.setFirstResult(0);
2024
		        }
2025
		    }
2026
			
2027
			List<SynonymRelationship> result = (List<SynonymRelationship>)query.getResultList();
2028
			defaultBeanInitializer.initializeAll(result, propertyPaths);
2029
			
2030
			// Ugly, but for now, there is no way to sort on a related entity property in Envers,
2031
			// and we can't live without this functionality in CATE as it screws up the whole 
2032
			// taxon tree thing
2033
			if(orderHints != null && !orderHints.isEmpty()) {
2034
			    SortedSet<SynonymRelationship> sortedList = new TreeSet<SynonymRelationship>(new SynonymRelationshipFromTaxonComparator());
2035
			    sortedList.addAll(result);
2036
			    return new ArrayList<SynonymRelationship>(sortedList);
2037
			}
2038
			
2039
			return result;
2040
		}
2041
	}
2042

    
2043

    
2044
	/* (non-Javadoc)
2045
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getUuidAndTitleCacheTaxon()
2046
	 */
2047
	@Override
2048
	public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
2049
		String queryString = String.format("select uuid, titleCache from %s where DTYPE = '%s'", type.getSimpleName(), Taxon.class.getSimpleName());
2050
		Query query = getSession().createQuery(queryString);
2051
		
2052
		List<UuidAndTitleCache<TaxonBase>> result = getUuidAndTitleCache(query);
2053
		
2054
		return result;
2055
	}
2056

    
2057

    
2058
	/* (non-Javadoc)
2059
	 * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao#getUuidAndTitleCacheSynonym()
2060
	 */
2061
	@Override
2062
	public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
2063
		String queryString = String.format("select uuid, titleCache from %s where DTYPE = '%s'", type.getSimpleName(), Synonym.class.getSimpleName());
2064
		Query query = getSession().createQuery(queryString);
2065
		
2066
		List<UuidAndTitleCache<TaxonBase>> result = getUuidAndTitleCache(query);
2067
		
2068
		return result;
2069
	}
2070

    
2071

    
2072

    
2073

    
2074
	
2075
	
2076
	
2077

    
2078
	
2079
	
2080
}
(3-3/4)