Project

General

Profile

Download (20.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2018 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.taxonGraph;
10

    
11
import java.util.ArrayList;
12
import java.util.EnumSet;
13
import java.util.HashSet;
14
import java.util.List;
15
import java.util.Set;
16
import java.util.UUID;
17

    
18
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
19
import org.hibernate.Session;
20
import org.hibernate.query.Query;
21

    
22
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
23
import eu.etaxonomy.cdm.model.metadata.CdmPreference;
24
import eu.etaxonomy.cdm.model.name.Rank;
25
import eu.etaxonomy.cdm.model.name.TaxonName;
26
import eu.etaxonomy.cdm.model.reference.Reference;
27
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
28
import eu.etaxonomy.cdm.model.reference.ReferenceType;
29
import eu.etaxonomy.cdm.model.taxon.Taxon;
30
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
31
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
32
import eu.etaxonomy.cdm.persistence.dao.common.IPreferenceDao;
33
import eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmPreferenceCache;
34
import eu.etaxonomy.cdm.persistence.dao.taxonGraph.TaxonGraphException;
35
import eu.etaxonomy.cdm.persistence.hibernate.TaxonGraphHibernateListener;
36

    
37
/**
38
 * Provides the business logic to manage multiple classifications as
39
 * classification fragments in a graph of
40
 * {@link eu.etaxonomy.cdm.model.taxon.Taxon Taxa} and
41
 * {@link eu.etaxonomy.cdm.model.taxon.TaxonRelationship TaxonRelationships}.
42
 * <p>
43
 * This abstract class provides the base for
44
 * {@link eu.etaxonomy.cdm.api.service.taxonGraph.TaxonGraphBeforeTransactionCompleteProcess}
45
 * and {@link TaxonGraphDaoHibernateImpl} which both are operating on the persisted
46
 * graph structures and thus are sharing this business logic in common:
47
 * <ul>
48
 * <li><code>TaxonGraphBeforeTransactionCompleteProcess</code>: Manages the
49
 * graph and is the only class allowed to modify it.</li>
50
 * <li><code>TaxonGraphDaoHibernateImpl</code>: Provides read only access to the
51
 * graph structure.</li>
52
 * <ul>
53
 * <p>
54
 * The conceptual idea for the resulting graph is described in <a href=
55
 * "https://dev.e-taxonomy.eu/redmine/issues/6173#6-N1T-Higher-taxon-graphs-with-includedIn-relations-taxon-relationships">#6173
56
 * 6) [N1T] Higher taxon-graphs with includedIn relations taxon
57
 * relationships}</a> The
58
 * <code>TaxonGraphBeforeTransactionCompleteProcess</code> is instantiated and
59
 * used in the {@link TaxonGraphHibernateListener}
60
 *
61
 * @author a.kohlbecker
62
 * @since Oct 4, 2018
63
 */
64
public abstract class AbstractHibernateTaxonGraphProcessor {
65

    
66
    private static final Logger logger = LogManager.getLogger(AbstractHibernateTaxonGraphProcessor.class);
67

    
68
    private EnumSet<ReferenceType> referenceSectionTypes = EnumSet.of(ReferenceType.Section, ReferenceType.BookSection);
69

    
70
    private Reference secReference = null;
71

    
72
    private TaxonRelationshipType relType = TaxonRelationshipType.TAXONOMICALLY_INCLUDED_IN();
73

    
74
    protected TaxonRelationshipType relType() {
75
        if(relType == null){
76
            relType = TaxonRelationshipType.TAXONOMICALLY_INCLUDED_IN();
77
        }
78
        return relType;
79
    }
80

    
81
    protected IPreferenceDao preferenceDao;
82

    
83
    public AbstractHibernateTaxonGraphProcessor(IPreferenceDao preferenceDao) {
84
        this.preferenceDao = preferenceDao;
85
    }
86

    
87
    public UUID getSecReferenceUUID(){
88
        CdmPreference pref = CdmPreferenceCache.instance(preferenceDao).get(TaxonGraphDaoHibernateImpl.CDM_PREF_KEY_SEC_REF_UUID);
89
        UUID uuid = null;
90
        if(pref != null && pref.getValue() != null){
91
            try {
92
                uuid = UUID.fromString(pref.getValue());
93
            } catch (Exception e) {
94
                // TODO is logging only ok?
95
                logger.error(e);
96
            }
97
        }
98
        if(uuid == null){
99
            logger.error("missing cdm property: " + TaxonGraphDaoHibernateImpl.CDM_PREF_KEY_SEC_REF_UUID.getSubject() + TaxonGraphDaoHibernateImpl.CDM_PREF_KEY_SEC_REF_UUID.getPredicate());
100
        }
101
        return uuid;
102
    }
103

    
104
    /**
105
     * Provides the sec reference for all taxa in the graph. The Reference uuid
106
     * is expected to be stored in the CDM perferences under the preference key
107
     * {@link TaxonGraphDaoHibernateImpl#CDM_PREF_KEY_SEC_REF_UUID}
108
     *
109
     * @return The reference for all taxa in the graph
110
     */
111
    public Reference secReference(){
112
        if(secReference == null){
113
            Query<Reference> q = getSession().createQuery("SELECT r FROM Reference r WHERE r.uuid = :uuid", Reference.class);
114
            q.setParameter("uuid", getSecReferenceUUID());
115
            secReference = q.uniqueResult();
116
            if(secReference == null){
117
                Reference missingRef = ReferenceFactory.newGeneric();
118
                UUID uuid = getSecReferenceUUID();
119
                if(uuid != null){
120
                    missingRef.setUuid(uuid);
121
                } else {
122
                    throw new RuntimeException("cdm preference " + TaxonGraphDaoHibernateImpl.CDM_PREF_KEY_SEC_REF_UUID.getSubject() + TaxonGraphDaoHibernateImpl.CDM_PREF_KEY_SEC_REF_UUID.getPredicate() + " missing, can not recover");
123
                }
124
                missingRef.setTitle("Autocreated missing reference for cdm property" + TaxonGraphDaoHibernateImpl.CDM_PREF_KEY_SEC_REF_UUID.getSubject() + TaxonGraphDaoHibernateImpl.CDM_PREF_KEY_SEC_REF_UUID.getPredicate());
125
                logger.warn("A reference with " + getSecReferenceUUID() + " does not exist in the database, and thus will be created now with the title "
126
                        + "\"" + missingRef.getTitle() + "\"");
127
                getSession().merge(missingRef);
128
            }
129
        } else {
130
            // make sure the entity is still in the current session
131
            secReference = getSession().load(Reference.class, secReference.getId());
132
        }
133
        return secReference;
134
    }
135

    
136

    
137
    /**
138
     * Create all missing edges from the <code>taxon</code> to names with higher
139
     * rank and edges from names with lower rank to this taxon. No longer needed
140
     * relations (edges) are removed.
141
     * <p>
142
     * {@link #conceptReference(Reference) concept references} which are null are ignored.
143
     * This means no edges are created.
144
     *
145
     *
146
     * @param taxon
147
     *            The taxon to update the edges for.
148
     */
149
    public void updateEdges(Taxon taxon) throws TaxonGraphException {
150

    
151
        Reference nomenclaturalReference = taxon.getName().getNomenclaturalReference();
152

    
153
        updateEdges(taxon, nomenclaturalReference);
154
    }
155

    
156
    /**
157
     * Create all missing edges from the <code>taxon</code> to names with higher
158
     * rank and edges from names with lower rank to this taxon. No longer needed
159
     * relations (edges) are removed.
160
     * <p>
161
     * {@link #conceptReference(Reference) concept references} which are null are ignored.
162
     * This means no edges are created.
163
     *
164
     *
165
     * @param taxon
166
     *            The taxon to update the edges for.
167
     * @param nomenclaturalReference
168
     *           The nomenclatural reference to update the edged with.
169
     */
170
    protected void updateEdges(Taxon taxon, Reference nomenclaturalReference) throws TaxonGraphException {
171

    
172
        Reference conceptReference = conceptReference(nomenclaturalReference);
173

    
174
        if(conceptReference != null){
175
            // update edges to higher names
176
            List<TaxonName> relatedHigherNames = relatedHigherNames(taxon.getName());
177
            List<TaxonRelationship> relationsFrom = taxonGraphRelationsFrom(taxon, conceptReference);
178
            List<TaxonName> relatedHigherNamesWithoutRels = new ArrayList<>(relatedHigherNames);
179
            for(TaxonRelationship rel : relationsFrom){
180
                boolean isRelToHigherName = relatedHigherNames.contains(rel.getToTaxon().getName());
181
                if(isRelToHigherName){
182
                    relatedHigherNamesWithoutRels.remove(rel.getToTaxon().getName());
183
                } else {
184
                    taxon.removeTaxonRelation(rel);
185
                }
186
            }
187
            for(TaxonName name : relatedHigherNamesWithoutRels){
188
                Taxon toTaxon = assureSingleTaxon(name);
189
                taxon.addTaxonRelation(toTaxon, relType(), conceptReference, null);
190
            }
191

    
192
            // update edges from lower names
193
            List<TaxonName> relatedLowerNames = relatedLowerNames(taxon.getName());
194
            List<TaxonRelationship> relationsTo = taxonGraphRelationsTo(taxon, null);
195
            List<TaxonName> relatedLowerNamesWithoutRels = new ArrayList<>(relatedLowerNames);
196
            for(TaxonRelationship rel : relationsTo){
197
                boolean isRelFromLowerName = relatedLowerNames.contains(rel.getFromTaxon().getName());
198
                if(isRelFromLowerName){
199
                    relatedLowerNamesWithoutRels.remove(rel.getFromTaxon().getName());
200
                } else {
201
                    taxon.removeTaxonRelation(rel);
202
                }
203
            }
204
            for(TaxonName name : relatedLowerNamesWithoutRels){
205
                Taxon fromTaxon = assureSingleTaxon(name);
206
                fromTaxon.addTaxonRelation(taxon, relType(), conceptReference, null);
207
            }
208
        }
209
    }
210

    
211
    /**
212
     * Remove all edges from the <code>taxon</code> having the <code>conceptReference</code>
213
     *
214
     * @param taxon
215
     * @param oldConceptReference
216
     */
217
    public void removeEdges(Taxon taxon, Reference conceptReference) {
218
        List<TaxonRelationship> relations = taxonGraphRelationsFrom(taxon, conceptReference);
219
        List<TaxonName> relatedHigherNames = relatedHigherNames(taxon.getName());
220
        for(TaxonRelationship rel : relations){
221
            boolean isRelToHigherName = relatedHigherNames.contains(rel.getToTaxon().getName());
222
            if(isRelToHigherName){
223
                taxon.removeTaxonRelation(rel);
224
            }
225
        }
226
    }
227

    
228
    /**
229
     * @param taxon
230
     */
231
    public void updateConceptReferenceInEdges(Taxon taxon, Reference oldNomReference) throws TaxonGraphException {
232

    
233
        Reference conceptReference = conceptReference(taxon.getName().getNomenclaturalReference());
234
        Reference oldConceptReference = conceptReference(oldNomReference);
235

    
236
        if(conceptReference != null && oldConceptReference != null){
237
            // update old with new ref
238
            updateReferenceInEdges(taxon, conceptReference, oldConceptReference);
239
        } else if(conceptReference != null && oldConceptReference == null) {
240
            // create new relations for the name as there are none so far
241
            updateEdges(taxon);
242
        } else if(conceptReference == null && oldConceptReference != null){
243
            // remove all relations
244
            removeEdges(taxon, oldConceptReference);
245
        }
246
    }
247

    
248
    /**
249
     * @param taxon
250
     * @param conceptReference
251
     * @param oldConceptReference
252
     */
253
    protected void updateReferenceInEdges(Taxon taxon, Reference conceptReference, Reference oldConceptReference) {
254
        List<TaxonRelationship> relations = taxonGraphRelationsFrom(taxon, oldConceptReference);
255
        List<TaxonName> relatedHigherNames = relatedHigherNames(taxon.getName());
256
        for(TaxonRelationship rel : relations){
257
            boolean isRelToHigherName = relatedHigherNames.contains(rel.getToTaxon().getName());
258
            if(isRelToHigherName){
259
                rel.setCitation(conceptReference);
260
                getSession().saveOrUpdate(rel);
261
            }
262
        }
263
    }
264

    
265
    abstract public Session getSession();
266

    
267
    /**
268
     * Same as {@link #assureSingleTaxon(TaxonName, boolean)} with
269
     * <code>createMissing = true</code>
270
     */
271
    public Taxon assureSingleTaxon(TaxonName taxonName) throws TaxonGraphException {
272
        return assureSingleTaxon(taxonName, true);
273
    }
274

    
275
    /**
276
     * Assurers that there is only one {@link Taxon} for the given name
277
     * (<code>taxonName</code>) having the the default sec reference
278
     * ({@link #getSecReferenceUUID()}).
279
     * <p>
280
     * If there is no such taxon it will be created when
281
     * <code>createMissing=true</code>. A <code>TaxonGraphException</code> is
282
     * thrown when more than one taxa with the default sec reference
283
     * ({@link #getSecReferenceUUID()}) are found for the given name
284
     * (<code>taxonName</code>)
285
     *
286
     * @param taxonName
287
     *            The name to check
288
     * @param createMissing
289
     *            A missing taxon is created when this is <code>true</code>.
290
     * @return
291
     * @throws TaxonGraphException
292
     *             A <code>TaxonGraphException</code> is thrown when more than
293
     *             one taxa with the default sec reference
294
     *             ({@link #getSecReferenceUUID()}) are found for the given name
295
     *             (<code>taxonName</code>)
296
     */
297
    public Taxon assureSingleTaxon(TaxonName taxonName, boolean createMissing) throws TaxonGraphException {
298

    
299
        UUID secRefUuid = getSecReferenceUUID();
300
        Session session = getSession();
301
        TaxonName taxonNamePersisted = session.load(TaxonName.class, taxonName.getId());
302

    
303
        // filter by secRefUuid
304
        Taxon taxon = null;
305
        Set<Taxon> taxa = new HashSet<>();
306
        for(Taxon t : taxonName.getTaxa()){
307
            if(t.getSec() != null && t.getSec().getUuid().equals(secRefUuid)){
308
                taxa.add(t);
309
            }
310
        }
311

    
312
        if(taxa.size() == 0){
313
            if(createMissing){
314
                if(taxonNamePersisted != null){
315
                    Reference secRef = secReference();
316
                    taxon = Taxon.NewInstance(taxonNamePersisted, secRef);
317
                    session.saveOrUpdate(taxon);
318
                } else {
319
                    throw new TaxonGraphException("Can't create taxon for deleted name: " + taxonName);
320
                }
321
            } else {
322
                if(logger.isDebugEnabled()){
323
                    logger.debug("No taxon found for " + taxonName);
324
                }
325
            }
326
        } else if(taxa.size() == 1){
327
            taxon = taxa.iterator().next();
328
        } else {
329
            throw new TaxonGraphException("A name to be used in a taxon graph must only have one taxon with the default sec reference [secRef uuid: "+ secRefUuid.toString() +"]");
330
        }
331
        return taxon != null ? session.load(Taxon.class, taxon.getId()) : null;
332
    }
333

    
334
    /**
335
     * Provides the concept reference for a given <code>nomenclaturalReference</code>.
336
     * For references which are {@link ReferenceType#Section} or {@link ReferenceType#BookSection} the in-reference is returned,
337
     * otherwise the passed  <code>nomenclaturalReference</code> itself.
338
     */
339
    protected Reference conceptReference(Reference nomenclaturalReference) {
340

    
341
        Reference conceptRef = nomenclaturalReference;
342
        if(conceptRef != null){
343
            while(referenceSectionTypes.contains(conceptRef.getType()) && conceptRef.getInReference() != null){
344
                conceptRef = conceptRef.getInReference();
345
            }
346
        }
347
        return conceptRef;
348
    }
349

    
350
    protected List<TaxonName> relatedHigherNames(TaxonName name) {
351

    
352
        List<TaxonName> relatedNames = new ArrayList<>();
353

    
354
        if(name.getRank().isSpecies() || name.getRank().isInfraSpecific()){
355
            if(name.getGenusOrUninomial() != null){
356
                List<TaxonName> names = listNamesAtRank(Rank.GENUS(), name.getGenusOrUninomial(), null);
357
                if(names.size() == 0){
358
                    logger.warn("Genus entity with \"" + name.getGenusOrUninomial() + "\" missing");
359
                } else {
360
                    if(names.size() > 1){
361
                        logger.warn("Duplicate genus entities found for \"" + name.getGenusOrUninomial() + "\", will create taxon graph relation to all of them!");
362
                    }
363
                    relatedNames.addAll(names);
364
                }
365
            }
366
        }
367
        if(name.getRank().isInfraSpecific()){
368
            if(name.getGenusOrUninomial() != null && name.getSpecificEpithet() != null){
369
                List<TaxonName> names = listNamesAtRank(Rank.SPECIES(), name.getGenusOrUninomial(), name.getSpecificEpithet());
370
                if(names.size() == 0){
371
                    logger.warn("Species entity with \"" + name.getGenusOrUninomial() + " " + name.getSpecificEpithet() + "\" missing");
372
                } else {
373
                    if(names.size() > 1){
374
                        logger.warn("Duplicate species entities found for \"" + name.getGenusOrUninomial() + " " + name.getSpecificEpithet() + "\", will create taxon graph relation to all of them!");
375
                    }
376
                    relatedNames.addAll(names);
377
                }
378
            }
379
         }
380

    
381
        return relatedNames;
382
    }
383

    
384
    protected List<TaxonName> relatedLowerNames(TaxonName name) {
385

    
386
        List<TaxonName> relatedNames = new ArrayList<>();
387
        if(name.getRank().isGenus()){
388
            if(name.getGenusOrUninomial() != null){
389
                List<TaxonName> names = listNamesAtRank(Rank.SPECIES(), name.getGenusOrUninomial(), null);
390
                if(names.size() == 0){
391
                    logger.debug("No species entity with \"" + name.getGenusOrUninomial() + " *\" found");
392
                } else {
393
                    logger.debug(names.size() + " species entities found with \"" + name.getGenusOrUninomial() + " *\"");
394
                    relatedNames.addAll(names);
395
                }
396
            }
397
        }
398
        if(name.getRank().isSpecies()){
399
            if(name.getGenusOrUninomial() != null && name.getSpecificEpithet() != null){
400
                List<TaxonName> names = listNamesBelowRank(Rank.SPECIES(), name.getGenusOrUninomial(), name.getSpecificEpithet());
401
                if(names.size() == 0){
402
                    logger.warn("No infraspecific entity with \"" + name.getGenusOrUninomial() + " " + name.getSpecificEpithet() + "*\" found");
403
                } else {
404
                    if(names.size() > 1){
405
                        logger.warn(names.size() + " infraspecific entities found with \"" + name.getGenusOrUninomial() + " " + name.getSpecificEpithet() + "*\"found");
406
                    }
407
                    relatedNames.addAll(names);
408
                }
409
            }
410
         }
411

    
412
        return relatedNames;
413
    }
414

    
415
    protected List<TaxonRelationship> taxonGraphRelationsFrom(Taxon taxon, Reference citation) {
416
        List<TaxonRelationship> relations = getTaxonRelationships(taxon, relType(), citation, TaxonRelationship.Direction.relatedFrom);
417
        return relations;
418
    }
419

    
420
    protected List<TaxonRelationship> taxonGraphRelationsTo(Taxon taxon, Reference citation) {
421
        List<TaxonRelationship> relations = getTaxonRelationships(taxon, relType(), citation, TaxonRelationship.Direction.relatedTo);
422
        return relations;
423
    }
424

    
425
    protected List<TaxonName> listNamesAtRank(Rank rank, String genusOrUninomial, String specificEpithet){
426
        String hql = "SELECT n FROM TaxonName n WHERE n.rank = :rank AND n.genusOrUninomial = :genusOrUninomial";
427
        if(specificEpithet != null){
428
            hql += " AND n.specificEpithet = :specificEpithet";
429
        }
430
        Query<TaxonName> q = getSession().createQuery(hql, TaxonName.class);
431

    
432
        q.setParameter("rank", rank);
433
        q.setParameter("genusOrUninomial", genusOrUninomial);
434
        if(specificEpithet != null){
435
            q.setParameter("specificEpithet", specificEpithet);
436
        }
437

    
438
        List<TaxonName> result = q.list();
439
        return result;
440
    }
441

    
442
    protected List<TaxonName> listNamesBelowRank(Rank rank, String genusOrUninomial, String specificEpithet){
443
        String hql = "SELECT n FROM TaxonName n WHERE n.rank.orderIndex > :rankOrderIndex AND n.genusOrUninomial = :genusOrUninomial";
444
        if(specificEpithet != null){
445
            hql += " AND n.specificEpithet = :specificEpithet";
446
        }
447
        Query<TaxonName> q = getSession().createQuery(hql, TaxonName.class);
448

    
449
        q.setParameter("rankOrderIndex", rank.getOrderIndex());
450
        q.setParameter("genusOrUninomial", genusOrUninomial);
451
        if(specificEpithet != null){
452
            q.setParameter("specificEpithet", specificEpithet);
453
        }
454

    
455
        List<TaxonName> result = q.list();
456
        return result;
457
    }
458

    
459
    /**
460
     * @param relatedTaxon required
461
     * @param type required
462
     * @param citation can be null
463
     * @param direction required
464
     */
465
    protected List<TaxonRelationship> getTaxonRelationships(Taxon relatedTaxon, TaxonRelationshipType type, Reference citation, Direction direction){
466

    
467
        getSession().flush();
468
        String hql = "SELECT rel FROM TaxonRelationship rel WHERE rel." + direction + " = :relatedTaxon AND rel.type = :type";
469
        if(citation != null){
470
            hql += " AND rel.source.citation = :citation";
471
        }
472
        Query<TaxonRelationship> q = getSession().createQuery(hql, TaxonRelationship.class);
473
        q.setParameter("relatedTaxon", relatedTaxon);
474
        q.setParameter("type", type);
475
        if(citation != null){
476
            q.setParameter("citation", citation);
477
        }
478
        List<TaxonRelationship> rels = q.list();
479
        return rels;
480
    }
481
}
(1-1/2)