/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.test.jdo; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import javax.jdo.PersistenceManager; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.zoodb.jdo.spi.PersistenceCapableImpl; import org.zoodb.test.testutil.TestTools; public class Test_081_SerializationBugs { @Before public void before() { // nothing } /** * Run after each test. */ @After public void after() { TestTools.closePM(); } @BeforeClass public static void beforeClass() { TestTools.createDb(); TestTools.defineSchema(PersistentTwitData.class, PaperPage.class, TwitInternalImpl.class); TestTools.defineSchema(SmallMap.class, SmallKey.class); } @AfterClass public static void afterClass() { TestTools.removeDb(); } /** * Test serialisation. */ @Test public void testSerialization() { PersistenceManager pm = TestTools.openPM(); pm.currentTransaction().begin(); PersistentTwitData map2 = new PersistentTwitData(); Set<TwitInternal> set1 = new HashSet<TwitInternal>(); set1.add(new TwitInternalImpl()); map2.twitsByPaperPage().put(new PaperPage("2", 1), set1); pm.makePersistent(map2); Object oid2 = pm.getObjectId(map2); pm.currentTransaction().commit(); TestTools.closePM(); //check target pm = TestTools.openPM(); pm.currentTransaction().begin(); //Check for content in target map2 = (PersistentTwitData) pm.getObjectById(oid2, true); pm.currentTransaction().rollback(); TestTools.closePM(); } /** * Test serialisation. * This tests a bug that caused a ConcurrentModificationException in the de-serializer * while de-serializing a SmallMap instance. * The problem is that in the end of the de-serialization, the map is filled with persistent * object. When their hashcode/equals method is called, another de-serialization is triggered * (because the key/value is not in the cache yet). This second de-serialization end up using * the same instance of the de-serializer, resulting in concurrent-modification exception * when iterating over the member 'mapsToFill'. * Fix: * - key in cache -> otherwise double de-serialization * - non re-entrant Deserializer, otherwise mapps are filled twice (not a functional bug, but a * performance problem. */ @Test public void testSerializationSmall() { PersistenceManager pm = TestTools.openPM(); pm.currentTransaction().begin(); SmallMap map1 = new SmallMap(); map1.map().put(new SmallKey(), null); pm.makePersistent(map1); Object oid1 = pm.getObjectId(map1); pm.currentTransaction().commit(); TestTools.closePM(); //check target pm = TestTools.openPM(); pm.currentTransaction().begin(); //Check for content in target map1 = (SmallMap) pm.getObjectById(oid1, true); pm.currentTransaction().rollback(); TestTools.closePM(); } } class SmallMap extends PersistenceCapableImpl { HashMap<SmallKey, SmallKey> map = new HashMap<SmallKey, SmallKey>(); public Map<SmallKey, SmallKey> map() { zooActivateWrite(); return map; } } class SmallKey extends PersistenceCapableImpl { int l; @Override public int hashCode() { zooActivateRead(); return super.hashCode(); } @Override public boolean equals(Object obj) { zooActivateRead(); return super.equals(obj); } } class Tag { } interface TwitInternal { } class ReceivedTwitImpl { } class PersistentTwitData extends PersistenceCapableImpl { private long nextId; private final Map<String, Tag> tagsByName; private final Map<Tag, Stack<TwitInternal>> twitsByTag; private final List<TwitInternal> twits; private final Map<PaperPage,Set<TwitInternal>> twitsByPaperPage; private final List<ReceivedTwitImpl> receivedTwits; public PersistentTwitData() { // this.tagsByName = new DBHashMap<String, TagImpl>(); // this.textsByTag = new DBHashMap<TagImpl, Stack<String>>(); this.tagsByName = new HashMap<String, Tag>(); this.twitsByTag = new HashMap<Tag, Stack<TwitInternal>>(); this.twits = new ArrayList<TwitInternal>(); this.twitsByPaperPage = new HashMap<PaperPage, Set<TwitInternal>>(); this.receivedTwits = new ArrayList<ReceivedTwitImpl>(); this.nextId = 0; } public Map<String, Tag> tagsByName() { // this.zooActivateRead(); this.zooActivateWrite(); return this.tagsByName; } public Map<Tag, Stack<TwitInternal>> twitsByTag() { // this.zooActivateRead(); this.zooActivateWrite(); return this.twitsByTag; } public Map<PaperPage, Set<TwitInternal>> twitsByPaperPage() { this.zooActivateWrite(); return this.twitsByPaperPage; } public List<TwitInternal> twits() { this.zooActivateWrite(); return this.twits; } public long nextId() { this.zooActivateWrite(); final long value = this.nextId; this.nextId++; return value; } public List<ReceivedTwitImpl> receivedTwits() { this.zooActivateWrite(); return this.receivedTwits; } } class PaperPage extends PersistenceCapableImpl { private String documentID; private int pageNumber; @SuppressWarnings("unused") private PaperPage() { //private, for ZooDB } public PaperPage(final String documentID, final int pageNumber) { this.documentID = documentID; this.pageNumber = pageNumber; } @Override public int hashCode() { this.zooActivateRead(); final int prime = 31; int result = 1; result = prime * result + (this.documentID == null ? 0 : this.documentID.hashCode()); result = prime * result + this.pageNumber; return result; } @Override public boolean equals(final Object obj) { this.zooActivateRead(); if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof PaperPage)) { return false; } final PaperPage other = (PaperPage) obj; if (this.documentID == null) { if (other.documentID != null) { return false; } } else if (!this.documentID.equals(other.documentID)) { return false; } if (this.pageNumber != other.pageNumber) { return false; } return true; } } class TwitInternalImpl extends PersistenceCapableImpl implements TwitInternal { @SuppressWarnings("unused") private Set<Tag> tags; TwitInternalImpl() { this.tags = new HashSet<Tag>(); } }