Project

General

Profile

Download (25.2 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.api.service.taxonGraph;
10

    
11
import java.util.Arrays;
12
import java.util.HashMap;
13
import java.util.Map;
14
import java.util.Objects;
15
import java.util.stream.Collectors;
16

    
17
import org.apache.commons.lang.ArrayUtils;
18
import org.apache.logging.log4j.Level;
19
import org.apache.logging.log4j.LogManager;
20
import org.apache.logging.log4j.Logger;
21
import org.hibernate.HibernateException;
22
import org.hibernate.Session;
23
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
24
import org.hibernate.engine.spi.SessionImplementor;
25
import org.hibernate.event.spi.PostInsertEvent;
26
import org.hibernate.event.spi.PostUpdateEvent;
27
import org.hibernate.event.spi.PreDeleteEvent;
28

    
29
import eu.etaxonomy.cdm.api.application.IRunAs;
30
import eu.etaxonomy.cdm.config.CdmHibernateListenerConfiguration;
31
import eu.etaxonomy.cdm.model.name.NomenclaturalSource;
32
import eu.etaxonomy.cdm.model.name.TaxonName;
33
import eu.etaxonomy.cdm.model.reference.Reference;
34
import eu.etaxonomy.cdm.model.taxon.Taxon;
35
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
36
import eu.etaxonomy.cdm.persistence.dao.common.IPreferenceDao;
37
import eu.etaxonomy.cdm.persistence.dao.hibernate.taxonGraph.AbstractHibernateTaxonGraphProcessor;
38
import eu.etaxonomy.cdm.persistence.dao.taxonGraph.TaxonGraphException;
39
import eu.etaxonomy.cdm.persistence.hibernate.TaxonGraphHibernateListener;
40

    
41
/**
42
 * A {@link BeforeTransactionCompletionProcess} implementation which manages
43
 * graphs of taxon relationships automatically. The graph consists of nodes
44
 * ({@link Taxon}) and edges ({@link TaxonRelationship}) where the taxa all have
45
 * the same {@link Taxon#getSec() sec reference}. The concept reference of a
46
 * classification is being projected onto the edge
47
 * ({@link TaxonRelationship}) having that reference as
48
 * {@link TaxonRelationship#getSource() TaxonRelationship.source.citation}.
49
 * <p>
50
 * The conceptual idea for the resulting graph is described in <a href=
51
 * "https://dev.e-taxonomy.eu/redmine/issues/6173#6-N1T-Higher-taxon-graphs-with-includedIn-relations-taxon-relationships">#6173
52
 * 6) [N1T] Higher taxon-graphs with includedIn relations taxon
53
 * relationships}</a> The
54
 * <code>TaxonGraphBeforeTransactionCompleteProcess</code> is instantiated and
55
 * used in the {@link TaxonGraphHibernateListener}
56
 * <p>
57
 * To activate this <code>BeforeTransactionCompletionProcess</code> class it needs to be registered at the
58
 * {@link TaxonGraphHibernateListener} like:
59
 * {@code taxonGraphHibernateListener.registerProcessClass(TaxonGraphBeforeTransactionCompleteProcess.class); }
60
 * <p>
61
 * On insert, update and delete events a new temporary session is being created
62
 * ({@link {@link #createTempSession(SessionImplementor)} to create, remove or
63
 * modify the nodes ({@link Taxon}) and edges ({@link TaxonRelationship}) of the
64
 * graph. The events on which the graph is modified are (see method descriptions
65
 * for more details on the processes being performed):
66
 * <ul>
67
 * <li>Change of a name or a names rank
68
 * ({@link #onNameOrRankChange(TaxonName)})</li>
69
 * <li>Creation of a new name ({@link #onNewTaxonName(TaxonName)})</li>
70
 * <li>Change of a nomenclatural reference
71
 * ({@link #onNomReferenceChange(TaxonName, Reference)})</li>
72
 *
73
 *
74
 * @see {@link TaxonGraphHibernateListener}
75
 * @see {@link CdmHibernateListenerConfiguration}
76
 * @see <a href=
77
 *      "https://dev.e-taxonomy.eu/redmine/issues/7648">https://dev.e-taxonomy.eu/redmine/issues/7648</a>
78
 *
79
 * @author a.kohlbecker
80
 * @since Sep 26, 2018
81
 */
82
public class TaxonGraphBeforeTransactionCompleteProcess
83
        extends AbstractHibernateTaxonGraphProcessor
84
        implements BeforeTransactionCompletionProcess {
85

    
86
    private static final Logger logger = LogManager.getLogger(TaxonGraphBeforeTransactionCompleteProcess.class);
87

    
88
    private static final String[] TAXONNAME_NAMEPARTS_OR_RANK_PROPS = new String[]{"genusOrUninomial", "specificEpithet", "rank"};
89
    private static final String[] TAXONNAME_NOMENCLATURALSOURCE = new String[]{"nomenclaturalSource"};
90
    private static final String NOMENCLATURALSOURCE_CITATION = "citation";
91
    private static final String NOMENCLATURALSOURCE_SOURCEDNAME = "sourcedName";
92
    private static final String[] CITATION_OR_SOURCEDNAME = new String[] {NOMENCLATURALSOURCE_CITATION, NOMENCLATURALSOURCE_SOURCEDNAME};
93

    
94
    private static boolean failOnMissingNomRef = false;
95

    
96
    public static boolean isFailOnMissingNomRef() {
97
        return failOnMissingNomRef;
98
    }
99
    public static void setFailOnMissingNomRef(boolean failOnMissingNomRef) {
100
        TaxonGraphBeforeTransactionCompleteProcess.failOnMissingNomRef = failOnMissingNomRef;
101
    }
102

    
103
    private Session temporarySession;
104

    
105
    private Level origLoggerLevel;
106

    
107
    private Session parentSession;
108

    
109
    private TaxonName taxonName;
110

    
111
    private NomenclaturalSource nomenclaturalSource;
112

    
113
    private String[] propertyNames;
114

    
115
    private Object[] oldState;
116

    
117
    private Object[] state;
118

    
119
    private int[] dirtyProperties;
120

    
121
    private EventType eventType;
122

    
123
    private enum EventType {
124
        INSERT, UPDATE, DELETE,
125
    }
126

    
127
    private IRunAs runAs = null;
128

    
129
    public TaxonGraphBeforeTransactionCompleteProcess(PostUpdateEvent event, IRunAs runAs, IPreferenceDao prefDao){
130
        super(prefDao);
131
        eventType = EventType.UPDATE;
132
        if(event.getEntity() instanceof TaxonName) {
133
            taxonName = (TaxonName) event.getEntity();
134
        } else
135
        if(event.getEntity() instanceof NomenclaturalSource) {
136
            nomenclaturalSource = (NomenclaturalSource) event.getEntity();
137
        } else {
138
            throw new RuntimeException("Either " + TaxonName.class.getName() + " or " + NomenclaturalSource.class.getName() + " are accepted");
139
        }
140
        propertyNames = event.getPersister().getPropertyNames();
141
        dirtyProperties = event.getDirtyProperties();
142
        oldState = event.getOldState();
143
        state = event.getState();
144
        this.runAs = runAs;
145
    }
146

    
147
    public TaxonGraphBeforeTransactionCompleteProcess(PostInsertEvent event, IRunAs runAs, IPreferenceDao prefDao) {
148
        super(prefDao);
149
        eventType = EventType.INSERT;
150
        if(event.getEntity() instanceof TaxonName) {
151
            taxonName = (TaxonName) event.getEntity();
152
        } else
153
        if(event.getEntity() instanceof NomenclaturalSource) {
154
            nomenclaturalSource = (NomenclaturalSource) event.getEntity();
155
        } else {
156
            throw new RuntimeException("Either " + TaxonName.class.getName() + " or " + NomenclaturalSource.class.getName() + " are accepted");
157
        }
158
        propertyNames = event.getPersister().getPropertyNames();
159
        dirtyProperties = null;
160
        oldState = null;
161
        state = event.getState();
162
        this.runAs = runAs;
163
    }
164

    
165
    public TaxonGraphBeforeTransactionCompleteProcess(PreDeleteEvent event, IRunAs runAs, IPreferenceDao prefDao) {
166
        super(prefDao);
167
        eventType = EventType.DELETE;
168
        if(event.getEntity() instanceof TaxonName) {
169
            taxonName = (TaxonName) event.getEntity();
170
        } else
171
        if(event.getEntity() instanceof NomenclaturalSource) {
172
            nomenclaturalSource = (NomenclaturalSource) event.getEntity();
173
        } else {
174
            throw new RuntimeException("Either " + TaxonName.class.getName() + " or " + NomenclaturalSource.class.getName() + " are accepted");
175
        }
176
        propertyNames = event.getPersister().getPropertyNames();
177
        dirtyProperties = null;
178
        oldState = event.getDeletedState();
179
        this.runAs = runAs;
180
    }
181

    
182
    @Override
183
    public void doBeforeTransactionCompletion(SessionImplementor session) {
184

    
185
        if(logger.isDebugEnabled()) {
186
            String message = eventType.name() + " for ";
187
            message += taxonName != null ? taxonName.toString() : "";
188
            message += nomenclaturalSource != null ? nomenclaturalSource.toString() : "";
189
            if(eventType.equals(EventType.UPDATE)){
190
                message += " with dirty properties: " + Arrays.stream(dirtyProperties).mapToObj(i -> propertyNames[i]).collect(Collectors.joining(", "));
191
            }
192
            logger.debug(message);
193
        }
194

    
195
        try {
196
            if(eventType.equals(EventType.INSERT)){
197
                // ---- INSERT ----
198
                if(taxonName != null) {
199
                    // 1. do the sanity checks first
200
                    if(taxonName.getNomenclaturalSource() == null || taxonName.getNomenclaturalSource().getCitation() == null) {
201
                        if(failOnMissingNomRef) {
202
                            throw new TaxonGraphException("TaxonName.nomenclaturalSource or TaxonName.nomenclaturalSource.citation must never be null.");
203
                        } else {
204
                            logger.warn("TaxonName.nomenclaturalSource or TaxonName.nomenclaturalSource.citation must never be null. (" + taxonName.toString() + ")");
205
                        }
206
                    }
207
                    createTempSession(session);
208
                    onNewTaxonName(taxonName);
209
                    getSession().flush();
210
                } else if(nomenclaturalSource != null) {
211
                    TaxonName taxonName =  (TaxonName)findValueByName(state, NOMENCLATURALSOURCE_SOURCEDNAME);
212
                    Reference reference =  (Reference)findValueByName(state, NOMENCLATURALSOURCE_CITATION);
213
                    if(taxonName != null && reference != null) {
214
                        createTempSession(session);
215
                        // load name and reference also into this session
216
                        taxonName = getSession().load(TaxonName.class, taxonName.getId());
217
                        reference = getSession().load(Reference.class, reference.getId());
218
                        onNewNomenClaturalSource(taxonName, reference);
219
                        getSession().flush();
220
                    }
221
                }
222
            } else if(eventType.equals(EventType.DELETE)) {
223
                if(taxonName != null) {
224
                    // handling this case explicitly should not be needed as this is expected to be done by orphan removal in
225
                    // hibernate
226
                    Reference reference =  (Reference)oldState[Arrays.binarySearch(propertyNames, TAXONNAME_NOMENCLATURALSOURCE)];
227
                    if(reference != null) {
228
                        createTempSession(session);
229
                        onTaxonNameDeleted(taxonName, reference);
230
                        getSession().flush();
231
                    }
232
                } else if(nomenclaturalSource != null) {
233
                   TaxonName taxonName =  (TaxonName)findValueByName(oldState, NOMENCLATURALSOURCE_SOURCEDNAME);
234
                   Reference reference =  (Reference)findValueByName(oldState, NOMENCLATURALSOURCE_CITATION);
235
                   if(taxonName != null && reference != null) {
236
                       createTempSession(session);
237
                       onNomReferenceRemoved(taxonName, reference);
238
                       getSession().flush();
239
                   }
240
                }
241

    
242
            } else {
243
                // ---- UPDATE ----
244
                // either taxonName or nomenclaturalSource not null, never both!
245
                if(taxonName != null) {
246
                    // 1. do the sanity checks first
247
                    Map<String, PropertyStateChange> changedNomenclaturalSourceProp = checkStateChange(TAXONNAME_NOMENCLATURALSOURCE);
248
                    if(!changedNomenclaturalSourceProp.isEmpty()) {
249
                         if(changedNomenclaturalSourceProp.get(TAXONNAME_NOMENCLATURALSOURCE[0]).newState == null) {
250
                             throw new TaxonGraphException("TaxonName.nomenclaturalSource must never be reverted to null.");
251
                         }
252
                         if(((NomenclaturalSource)changedNomenclaturalSourceProp.get(TAXONNAME_NOMENCLATURALSOURCE[0]).newState).getCitation() == null){
253
                             throw new TaxonGraphException("TaxonName.nomenclaturalSource.citation must never be reverted to null.");
254
                         }
255
                         createTempSession(session);
256
                         NomenclaturalSource oldNomenclaturalSource = (NomenclaturalSource)changedNomenclaturalSourceProp.get(TAXONNAME_NOMENCLATURALSOURCE[0]).oldState;
257
                         onNomReferenceChange(taxonName, oldNomenclaturalSource.getCitation());
258
                         getSession().flush();
259
                    }
260
                    // 2. update the graph
261
                    Map<String, PropertyStateChange> changedProps = checkStateChange(TAXONNAME_NAMEPARTS_OR_RANK_PROPS);
262
                    if(!changedProps.isEmpty()){
263
                        createTempSession(session);
264
                        onNameOrRankChange(taxonName);
265
                        getSession().flush();
266
                    }
267
                } else
268
                if(nomenclaturalSource != null) {
269
                    Map<String, PropertyStateChange> changedProps = checkStateChange(CITATION_OR_SOURCEDNAME);
270
                    if(!changedProps.isEmpty()){
271
                        TaxonName newTaxonNameState = null;
272
                        Reference newCitationState = null;
273
                        TaxonName oldTaxonNameState = null;
274
                        Reference oldCitationState = null;
275
                        if(changedProps.containsKey(NOMENCLATURALSOURCE_SOURCEDNAME)) {
276
                            newTaxonNameState = (TaxonName) changedProps.get(NOMENCLATURALSOURCE_SOURCEDNAME).newState;
277
                            oldTaxonNameState = (TaxonName) changedProps.get(NOMENCLATURALSOURCE_SOURCEDNAME).oldState;
278
                        }
279
                        if(changedProps.containsKey(NOMENCLATURALSOURCE_CITATION)) {
280
                            newCitationState = (Reference) changedProps.get(NOMENCLATURALSOURCE_CITATION).newState;
281
                            oldCitationState = (Reference) changedProps.get(NOMENCLATURALSOURCE_CITATION).oldState;
282
                        }
283
                        // 1. do the sanity checks first
284
                        if(oldTaxonNameState != null && oldTaxonNameState.getNomenclaturalSource() == null) {
285
                            createTempSession(session);
286
                            onNomReferenceChange(oldTaxonNameState, null);
287
                            getSession().flush();
288
                        }
289
                        // 2. update the graph
290
                        if(newTaxonNameState != null && newCitationState == null) {
291
                            createTempSession(session);
292
                            onNomReferenceChange(newTaxonNameState, nomenclaturalSource.getCitation());
293
                            getSession().flush();
294
                        }
295
                        if(newTaxonNameState == null && newCitationState != null) {
296
                            createTempSession(session);
297
                            onNomReferenceChange(nomenclaturalSource.getSourcedName(), oldCitationState);
298
                            getSession().flush();
299
                        }
300
                    }
301
                }
302
            }
303
      } catch (TaxonGraphException e) {
304
          throw new HibernateException(e);
305
      }
306
      finally {
307
          if (getSession() != null ) {
308
              // temporarySession.close(); // no need to close the session since the session is configured for auto close, see createTempSession()
309
          }
310
      }
311
    }
312

    
313
    private Map<String, PropertyStateChange> checkStateChange(String[] propertyNamesToCheck){
314

    
315
        Map<String, PropertyStateChange> changedProps = new HashMap<>();
316

    
317
        if(dirtyProperties == null){
318
            return changedProps;
319
        }
320
        for(int i : dirtyProperties){
321
            if(ArrayUtils.contains(propertyNamesToCheck, propertyNames[i])){
322

    
323
                if(!Objects.equals(oldState[i], state[i])){
324
                    // here we check again, this should not be needed as we should
325
                    // be able to rely on that this check is true for all
326
                    // indices in dirtyProperties
327
                    changedProps.put(propertyNames[i], (new PropertyStateChange(oldState[i], state[i], i)));
328
                }
329
            }
330
        }
331
        return changedProps;
332
    }
333

    
334
    private Object findValueByName(Object[] states, String propertyName) {
335
        int key = -1;
336
        for(int i = 0; i < propertyNames.length; i++) {
337
            if(propertyNames[i].equals(propertyName)) {
338
                key = i;
339
                break;
340
            }
341
        }
342
        if(key > -1) {
343
            return states[key];
344
        }
345
        return null;
346
    }
347

    
348
    /**
349
     * Concept of creation of sub-sessions found in
350
     * AuditProcess.doBeforeTransactionCompletion(SessionImplementor session)
351
     * and adapted to make it work for this case.
352
     *
353
     * @param session
354
     */
355
    protected void createTempSession(SessionImplementor session) {
356
        if(getSession() == null){
357
            parentSession = session;
358
            temporarySession = ((Session) session)
359
                      .sessionWithOptions().transactionContext()
360
                      // in contrast to AuditProcess.doBeforeTransactionCompletion we need the session to close automatically,
361
                      // otherwise the hibernate search indexer will suffer from LazyInitializationExceptions
362
                      .autoClose(true)
363
                      // in contrast to AuditProcess.doBeforeTransactionCompletion, the ConnectionReleaseMode.AFTER_TRANSACTION causes problems for us:
364
                      //.connectionReleaseMode( ConnectionReleaseMode.AFTER_TRANSACTION )
365
                      .openSession();
366
//            origLoggerLevel = Logger.getLogger("org.hibernate.SQL").getLevel();
367
//            Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
368
        }
369
    }
370

    
371
    /**
372
     * Same as {@link #onNameOrRankChange(TaxonName)}
373
     */
374
    public void onNewTaxonName(TaxonName taxonName) throws TaxonGraphException {
375
        onNameOrRankChange(taxonName);
376
    }
377

    
378
    /**
379
     * Create a taxon with graph sec reference {@link #secReference()) for the
380
     * <code>taxonName</code> if not yet existing and updates the edges
381
     * from and to this taxon by creating missing and removing obsolete ones.
382
     *
383
     * @throws TaxonGraphException
384
     *             A <code>TaxonGraphException</code> is thrown when more than
385
     *             one taxa with the default sec reference
386
     *             ({@link #getSecReferenceUUID()}) are found for the given name
387
     *             (<code>taxonName</code>). Originates from
388
     *             {@link #assureSingleTaxon(TaxonName, boolean)}
389
     */
390
    public void onNameOrRankChange(TaxonName taxonName) throws TaxonGraphException {
391
        boolean isNotDeleted = parentSession.contains(taxonName) && taxonName.isPersited();
392
        // TODO use audit event to check for deletion?
393
        if(isNotDeleted){
394
            try{
395
                if(runAs != null){
396
                    runAs.apply();
397
                }
398
                Taxon taxon = assureSingleTaxon(taxonName);
399
                updateEdges(taxon);
400
                getSession().saveOrUpdate(taxon);
401
            } finally {
402
                if(runAs != null){
403
                    runAs.restore();
404
                }
405
            }
406
        }
407
    }
408

    
409
    /**
410
     * This method manages updates to edges from and to a taxon which
411
     * reflects the concept reference of the original classification. The
412
     * concept reference of each classification is being projected onto the
413
     * graph as edge ({@link TaxonRelationship}) having that reference as
414
     * {@link TaxonRelationship#getSource() TaxonRelationship.source.citation}.
415
     * <p>
416
     * Delegates to {@link #onNewTaxonName(TaxonName)} in case the
417
     * <code>oldNomReference</code> is <code>null</code>. Otherwise the
418
     * {@link #assureSingleTaxon(TaxonName)} check is performed to create the
419
     * taxon if missing and the concept reference in the edges
420
     * <code>source</code> field is finally updated.
421
     *
422
     * @param taxonName
423
     *   The updated taxon name having a new nomenclatural reference
424
     * @param oldNomReference
425
     *   The nomenclatural reference as before the update of the <code>taxonName</code>
426
     * @throws TaxonGraphException
427
     *             A <code>TaxonGraphException</code> is thrown when more than
428
     *             one taxa with the default sec reference
429
     *             ({@link #getSecReferenceUUID()}) are found for the given name
430
     *             (<code>taxonName</code>). Originates from
431
     *             {@link #assureSingleTaxon(TaxonName, boolean)}
432
     */
433
    public void onNomReferenceChange(TaxonName taxonName, Reference oldNomReference) throws TaxonGraphException {
434
        if(oldNomReference == null){
435
            onNewTaxonName(taxonName);
436
        }
437
        boolean isNotDeleted = parentSession.contains(taxonName) && taxonName.isPersited();
438
        // TODO use audit event to check for deletion?
439
        if(isNotDeleted){
440
            try {
441
                if(runAs != null){
442
                    runAs.apply();
443
                }
444
                Taxon taxon = assureSingleTaxon(taxonName);
445
                updateConceptReferenceInEdges(taxon, oldNomReference);
446
                getSession().saveOrUpdate(taxon);
447
            } finally {
448
                if(runAs != null){
449
                    runAs.restore();
450
                }
451
            }
452
        }
453
    }
454

    
455
    /**
456
     * This method manages updates to edges from and to a taxon which
457
     * reflects the concept reference of the original classification. The
458
     * concept reference of each classification is being projected onto the
459
     * graph as edge ({@link TaxonRelationship}) having that reference as
460
     * {@link TaxonRelationship#getSource() TaxonRelationship.source.citation}.
461
     *
462
     *
463
     * @param taxonName
464
     *   The updated taxon name having a new nomenclatural reference
465
     * @param nomReference
466
     *   The new nomenclatural reference to be assigned to the <code>taxonName</code>
467
     * @throws TaxonGraphException
468
     *             A <code>TaxonGraphException</code> is thrown when more than
469
     *             one taxa with the default sec reference
470
     *             ({@link #getSecReferenceUUID()}) are found for the given name
471
     *             (<code>taxonName</code>). Originates from
472
     *             {@link #assureSingleTaxon(TaxonName, boolean)}
473
     */
474
    public void onNewNomenClaturalSource(TaxonName taxonName, Reference nomReference) throws TaxonGraphException {
475

    
476
        try {
477
            if(runAs != null){
478
                runAs.apply();
479
            }
480
            Taxon taxon = assureSingleTaxon(taxonName);
481
            updateEdges(taxon, nomReference);
482
            getSession().saveOrUpdate(taxon);
483
        } finally {
484
            if(runAs != null){
485
                runAs.restore();
486
            }
487
        }
488
    }
489

    
490
    /**
491
     * Manages removals of references which require the deletion of edges from and to
492
     * the taxon which reflects the concept reference of the original classification.
493
     *
494
     * @param taxonName
495
     *   The taxon name which formerly had the the nomenclatural reference
496
     * @param oldNomReference
497
     *   The nomenclatural reference which was removed from the <code>taxonName</code>
498
     * @throws TaxonGraphException
499
     */
500
    public void onNomReferenceRemoved(TaxonName taxonName, Reference oldNomReference) throws TaxonGraphException {
501

    
502
        boolean isNotDeleted = parentSession.contains(taxonName) && taxonName.isPersited();
503
        // TODO use audit event to check for deletion?
504
        if(isNotDeleted){
505
            try {
506
                if(runAs != null){
507
                    runAs.apply();
508
                }
509
                Taxon taxon = assureSingleTaxon(taxonName, false);
510
                if(taxon != null) {
511
                    removeEdges(taxon, oldNomReference);
512
                }
513
                getSession().saveOrUpdate(taxon);
514
            } finally {
515
                if(runAs != null){
516
                    runAs.restore();
517
                }
518
            }
519
        }
520
    }
521

    
522
    /**
523
     * Manages deletions of taxonNames which requires the deletion of edges and the
524
     * taxon which reflects the concept reference of the original classification.
525
     *
526
     * @param taxonName
527
     *   The taxon name which formerly had the the nomenclatural reference
528
     * @param oldNomReference
529
     *   The nomenclatural reference which was removed from the <code>taxonName</code>
530
     * @throws TaxonGraphException
531
     */
532
    private void onTaxonNameDeleted(TaxonName taxonName, Reference reference) throws TaxonGraphException {
533

    
534
        try {
535
            if(runAs != null){
536
                runAs.apply();
537
            }
538
            Taxon taxon = assureSingleTaxon(taxonName, false);
539
            if(taxon != null) {
540
                removeEdges(taxon, reference);
541
                getSession().delete(taxon);
542
            }
543
            getSession().saveOrUpdate(taxon);
544
        } finally {
545
            if(runAs != null){
546
                runAs.restore();
547
            }
548
        }
549
    }
550

    
551
    @Override
552
    public Session getSession() {
553
        return temporarySession;
554
    }
555

    
556
    public static class PropertyStateChange {
557

    
558
        Object oldState;
559
        Object newState;
560
        int index;
561

    
562
        public PropertyStateChange(Object oldState, Object newState, int index) {
563
            super();
564
            this.oldState = oldState;
565
            this.newState = newState;
566
            this.index = index;
567
        }
568
    }
569
}
(2-2/3)