Merge branch 'release/5.45.0'
[cdmlib.git] / cdmlib-services / src / test / java / eu / etaxonomy / cdm / api / service / HandlingCdmEntitiesTest.java
1 /**
2 * Copyright (C) 2014 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;
10
11 import java.util.Arrays;
12 import java.util.List;
13 import java.util.UUID;
14
15 import org.apache.logging.log4j.LogManager;
16 import org.apache.logging.log4j.Logger;
17 import org.hibernate.LazyInitializationException;
18 import org.junit.Assert;
19 import org.junit.Test;
20 import org.unitils.database.annotations.Transactional;
21 import org.unitils.database.util.TransactionMode;
22 import org.unitils.dbunit.annotation.DataSet;
23 import org.unitils.spring.annotation.SpringBeanByType;
24
25 import eu.etaxonomy.cdm.model.agent.Person;
26 import eu.etaxonomy.cdm.model.agent.Team;
27 import eu.etaxonomy.cdm.model.common.Annotation;
28 import eu.etaxonomy.cdm.model.common.CdmBase;
29 import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
30 import eu.etaxonomy.cdm.model.description.Feature;
31 import eu.etaxonomy.cdm.model.description.TaxonDescription;
32 import eu.etaxonomy.cdm.model.description.TextData;
33 import eu.etaxonomy.cdm.model.name.IBotanicalName;
34 import eu.etaxonomy.cdm.model.name.INonViralName;
35 import eu.etaxonomy.cdm.model.name.TaxonName;
36 import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
37 import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
38 import eu.etaxonomy.cdm.model.reference.Reference;
39 import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
40 import eu.etaxonomy.cdm.model.taxon.Taxon;
41 import eu.etaxonomy.cdm.test.integration.CdmIntegrationTest;
42
43 /**
44 * This test class tries to describe how hibernate works with objects (save, update, merge).
45 *
46 * @author cmathew
47 * @since 17 Sep 2014
48 */
49 public class HandlingCdmEntitiesTest extends CdmIntegrationTest {
50
51 @SuppressWarnings("unused")
52 private static final Logger logger = LogManager.getLogger();
53
54 private static final String LIE_TEAMMEMBERS_NOSESSION = "failed to lazily initialize a collection of role: eu.etaxonomy.cdm.model.agent.Team.teamMembers, could not initialize proxy - no Session";
55 private static final String LIE_NOSESSION = "could not initialize proxy .* no Session";
56
57 private static final UUID taxonUuid = UUID.fromString("23c35977-01b5-452c-9225-ecce440034e0");
58
59 @SpringBeanByType
60 private IReferenceService referenceService;
61
62 @SpringBeanByType
63 private IAnnotationService annotationService;
64
65 @SpringBeanByType
66 private ITaxonService taxonService;
67
68 @SpringBeanByType
69 private IAgentService agentService;
70
71 @SpringBeanByType
72 private ICommonService commonService;
73
74 public static final String[] includeTables = new String[]{
75 "TAXONBASE",
76 "TAXONNAME",
77 "AGENTBASE",
78 "AGENTBASE_AGENTBASE",
79 "HOMOTYPICALGROUP"
80 };
81
82 @Override
83 @Transactional(TransactionMode.DISABLED)
84 public final void createTestDataSet() {
85 Team combAuthor = Team.NewTitledInstance("Avengers", "Avengers");
86 combAuthor.addTeamMember(Person.NewTitledInstance("Iron Man"));
87 IBotanicalName name = TaxonNameFactory.NewBotanicalInstance(null, "Abies alba", null, null, null, null, null, null, null);
88 name.setCombinationAuthorship(combAuthor);
89 Taxon taxon = Taxon.NewInstance(name, null);
90 taxonService.save(taxon).getUuid();
91 printDataSet(System.out,false,null,includeTables);
92 }
93
94 @Test
95 @DataSet
96 @Transactional(TransactionMode.DISABLED)
97 public void testNonTransactionalUpdateForExistingTaxon() {
98 // this method tests the updating of a 'truely' detached object which
99 // attempts to initialize a lazy loaded proxy object while trying to
100 // update the same.
101
102 // setting the TransactionMode for this method to DISABLED is important
103 // to ensure that transaction boundaries remain at the service layer calls
104 // to simulate detachment and update of the persisted object
105
106 // ---- loading taxon with find (uuid) ----
107
108 Taxon taxon = (Taxon)taxonService.find(taxonUuid);
109
110 // at this point the taxonNew object is detached and all lazy loaded proxy
111 // objects in the object graph (including teamMembers) will have values of
112 // initialized=false and session=null
113
114 // since name is lazy loaded the call to getName should fail
115 try {
116 CdmBase.deproxy(taxon.getName(), TaxonName.class);
117 Assert.fail("LazyInitializationException not thrown for lazy loaded Taxon.name");
118 } catch(LazyInitializationException lie) {
119 assertIsLieNoSession(lie);
120 }
121
122 // ---- loading taxon with find (id) ----
123
124 taxon = commonService.find(taxon.getClass(), taxon.getId());
125
126 // at this point the taxonNew object is detached and all lazy loaded proxy
127 // objects in the object graph (including teamMembers) will have values of
128 // initialized=false and session=null
129
130 // since name is lazy loaded the call to getName should fail
131 try {
132 CdmBase.deproxy(taxon.getName(),TaxonName.class);
133 Assert.fail("LazyInitializationException not thrown for lazy loaded Taxon.name");
134 } catch(LazyInitializationException lie) {
135 assertIsLieNoSession(lie);
136 }
137
138 // ---- loading taxon with findTaxonByUuid ----
139
140 List<String> TAXON_INIT_STRATEGY = Arrays.asList(new String[] {
141 "name"
142 });
143
144 // loading the taxon with its taxon name object pre-initialized
145 taxon = (Taxon)taxonService.findTaxonByUuid(taxonUuid, TAXON_INIT_STRATEGY);
146
147 // at this point the taxonNew object is detached and all lazy loaded proxy
148 // objects in the object graph (including teamMembers) will have values of
149 // initialized=false and session=null
150
151 TaxonName nvn = CdmBase.deproxy(taxon.getName());
152
153 // normally this call should throw a lazy loading exception since
154 // the combinationAuthorship object is not initialized, but
155 // the findTaxonByUuid performs this initialization (via the
156 // AdvancedBeanInitializer) since the combinationAuthorship
157 // is annotated with CacheUpdate
158
159 Team team = CdmBase.deproxy(nvn.getCombinationAuthorship(),Team.class);
160
161 // setting the protected title cache to false to ensure that
162 // TeamDefaultCacheStrategy.getTitleCache is called, which in turn tries
163 // to initialize the teamMembers persistent collection which fails
164
165 team.setProtectedTitleCache(false);
166
167 try {
168 taxonService.update(taxon);
169 Assert.fail("LazyInitializationException not thrown for lazy loaded Team.teamMembers");
170 } catch(LazyInitializationException lie) {
171
172 if(!lie.getMessage().equals(LIE_TEAMMEMBERS_NOSESSION)) {
173 Assert.fail("LazyInitializationException thrown, but not : " + LIE_TEAMMEMBERS_NOSESSION);
174 }
175 }
176
177 // the above fails due to the fact that hibernate does not resolve lazy
178 // loading on a detached object until the object is persisted. The attempt
179 // to initialize teamMembers before the object graph is persisted means that
180 // the current session is not yet attached to the proxy objects and hibernate
181 // tries to use the existing session set in the teamMembers object which is
182 // null, leading to the exception
183
184 // setting the protected title cache to true to ensure that
185 // TeamDefaultCacheStrategy.getTitleCache is not called, implying
186 // that the teamMembers are not initialized, so no exception is thrown
187
188 team.setProtectedTitleCache(true);
189 team.setProtectedCollectorTitleCache(true);
190 team.setProtectedNomenclaturalTitleCache(true);
191 taxonService.update(taxon);
192
193 }
194
195 private void assertIsLieNoSession(LazyInitializationException lie) {
196 if(!lie.getMessage().matches(LIE_NOSESSION)) {
197 Assert.fail("LazyInitializationException ("+lie.getMessage()+") thrown, but does not match: " + LIE_NOSESSION);
198 }
199 }
200
201 @Test
202 @DataSet
203 public void testTransactionalUpdateAfterFindTaxonByUuidForExistingTaxon() {
204 // this method tests the updating of a detached object inside a single
205 // transaction.
206
207 // this method is transactional, meaning that a transaction is started
208 // at the start of the method and ends only with the end of the method
209
210 // since this method is transactional, any object initialized within this method
211 // will have a valid session attached to any lazy loaded proxy objects
212 // in the object graph (including teamMembers)
213
214 // ---- loading taxon with find (uuid) ----
215
216 Taxon taxon = (Taxon)taxonService.find(taxonUuid);
217
218 // at this point the taxon object is detached and all lazy loaded proxy
219 // objects in the object graph (including teamMembers) will have a new
220 // session attached implying that all the following calls will succeed
221
222 INonViralName nvn = CdmBase.deproxy(taxon.getName());
223 Team team = CdmBase.deproxy(nvn.getCombinationAuthorship(),Team.class);
224 taxonService.update(taxon);
225
226 // ---- loading taxon with find (id) ----
227
228 taxon = commonService.find(taxon.getClass(), taxon.getId());
229
230 // at this point the taxon object is detached and all lazy loaded proxy
231 // objects in the object graph (including teamMembers) will have a new
232 // session attached implying that all the following calls will succeed
233
234 nvn = CdmBase.deproxy(taxon.getName());
235 team = CdmBase.deproxy(nvn.getCombinationAuthorship(), Team.class);
236 taxonService.update(taxon);
237
238 // ---- loading taxon with findTaxonByUuid ----
239
240 List<String> TAXON_INIT_STRATEGY = Arrays.asList(new String[] {
241 "name"
242 });
243
244 // loading the taxon with its taxon name object pre-initialized
245 taxon = (Taxon)taxonService.findTaxonByUuid(taxonUuid, TAXON_INIT_STRATEGY);
246
247 nvn = CdmBase.deproxy(taxon.getName(),TaxonName.class);
248 team = CdmBase.deproxy(nvn.getCombinationAuthorship(), Team.class);
249
250 // since a valid session is now attached to teamMembers, forcing the
251 // initializing of the teamMembers (in TeamDefaultCacheStrategy.getTitleCache)
252 // by setting the protected title cache to false does not throw an exception
253 // because the teamMember persistent collection now has a valid session,
254 // which is used to initialize the persistent collection
255
256 team.setProtectedTitleCache(false);
257 taxonService.update(taxon);
258 }
259
260 @Test
261 @Transactional(TransactionMode.DISABLED)
262 public void testNonTransactionalUpdateForNewTaxon() {
263
264 // this test is only to prove that the update problem occurs
265 // also for newly created objects (as expected)
266
267 // create / save new taxon with name and author team with team member
268
269 Team combAuthor = Team.NewTitledInstance("X-Men", "X-Men");
270 combAuthor.addTeamMember(Person.NewTitledInstance("Wolverine"));
271
272
273 IBotanicalName name = TaxonNameFactory.NewBotanicalInstance(null, "Pinus Alba", null, null, null, null, null, null, null);
274 name.setCombinationAuthorship(combAuthor);
275
276 Taxon taxon = Taxon.NewInstance(name, null);
277
278 UUID taxonUuid = taxonService.save(taxon).getUuid();
279
280 List<String> TAXON_INIT_STRATEGY = Arrays.asList(new String[] {
281 "name"
282 });
283
284 taxon = (Taxon)taxonService.findTaxonByUuid(taxonUuid, TAXON_INIT_STRATEGY);
285
286 INonViralName nvn = CdmBase.deproxy(taxon.getName());
287 Team team = CdmBase.deproxy(nvn.getCombinationAuthorship(), Team.class);
288 team.setProtectedTitleCache(false);
289
290 try {
291 taxonService.update(taxon);
292 Assert.fail("LazyInitializationException not thrown for lazy loaded Team.teamMembers");
293 } catch(LazyInitializationException lie) {
294
295 if(!lie.getMessage().equals(LIE_TEAMMEMBERS_NOSESSION)) {
296 Assert.fail("LazyInitializationException thrown, but not : " + LIE_TEAMMEMBERS_NOSESSION);
297 }
298 }
299
300 }
301
302 @Test
303 @DataSet
304 @Transactional(TransactionMode.DISABLED)
305 public void testNonTransactionalMergeForExistingTaxon() {
306 // this method tests the updating of a 'truely' detached object
307 // using merge
308
309 // setting the TransactionMode for this method to DISABLED is important
310 // to ensure that transaction boundaries remain at the service layer calls
311 // to simulate detachment and update of the persisted object
312
313 // ---- loading taxon with find (uuid) ----
314
315 Taxon taxon = (Taxon)taxonService.find(taxonUuid);
316
317 // at this point the taxonNew object is detached and all lazy loaded proxy
318 // objects in the object graph (including teamMembers) will have values of
319 // initialized=false and session=null
320
321 taxonService.merge(taxon);
322
323 // ---- loading taxon with find (id) ----
324
325 taxon = commonService.find(taxon.getClass(), taxon.getId());
326
327 taxonService.merge(taxon);
328
329 // ---- loading taxon with findTaxonByUuid ----
330
331 List<String> TAXON_INIT_STRATEGY = Arrays.asList(new String[] {
332 "name"
333 });
334
335 // loading the taxon with its taxon name object pre-initialized
336 taxon = (Taxon)taxonService.findTaxonByUuid(taxonUuid, TAXON_INIT_STRATEGY);
337
338 // at this point the taxonNew object is detached and all lazy loaded proxy
339 // objects in the object graph (including teamMembers) will have values of
340 // initialized=false and session=null
341
342 INonViralName nvn = CdmBase.deproxy(taxon.getName());
343
344 // normally this call should throw a lazy loading exception since
345 // the combinationAuthorship object is not initialized, but
346 // the findTaxonByUuid performs this initialization (via the
347 // AdvancedBeanInitializer) since the combinationAuthorship
348 // is annotated with CacheUpdate
349
350 Team team = CdmBase.deproxy(nvn.getCombinationAuthorship(),Team.class);
351
352 // setting the protected title cache to false to ensure that
353 // TeamDefaultCacheStrategy.getTitleCache is called, which in turn tries
354 // to initialize the teamMembers persistent collection which fails
355 team.setProtectedTitleCache(false);
356
357
358 taxonService.merge(taxon);
359
360 }
361
362
363 @Test
364 public final void testTaxonDescriptionMerge() {
365
366 IBotanicalName name = TaxonNameFactory.NewBotanicalInstance(null, "Abies alba", null, null, null, null, null, null, null);
367 Taxon taxon = Taxon.NewInstance(name, null);
368 TaxonDescription description = TaxonDescription.NewInstance(taxon);
369
370 TextData textData = TextData.NewInstance();
371
372 textData.setFeature(Feature.ECOLOGY());
373 description.addElement(textData);
374
375 DescriptionElementSource descriptionElementSource = DescriptionElementSource.NewInstance(OriginalSourceType.PrimaryTaxonomicSource);
376
377 textData.addSource(descriptionElementSource);
378
379 taxonService.merge(taxon);
380 }
381
382 @Test //testing of bidirectionality of supplemental data #5743
383 public final void testReferenceWithAnnotationMerge() {
384
385 Reference ref = ReferenceFactory.newBook();
386
387 ref.addAnnotation(Annotation.NewDefaultLanguageInstance("ref"));
388
389 referenceService.merge(ref);
390 }
391
392 @Test //testing of bidirectionality of supplemental data #5743
393 public final void testAnnotationMerge() {
394
395 Reference ref = ReferenceFactory.newBook();
396
397 Annotation annotation = Annotation.NewDefaultLanguageInstance("anno");
398 ref.addAnnotation(annotation);
399
400 annotationService.merge(annotation);
401 }
402 }