/*
* #!
* Ontopia DB2TM
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* !#
*/
package net.ontopia.topicmaps.db2tm;
import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.sql.Statement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import net.ontopia.infoset.core.LocatorIF;
import net.ontopia.infoset.impl.basic.URILocator;
import net.ontopia.topicmaps.impl.basic.InMemoryTopicMapStore;
import net.ontopia.topicmaps.xml.CanonicalXTMWriter;
import net.ontopia.persistence.proxy.DefaultConnectionFactory;
import net.ontopia.topicmaps.core.TopicIF;
import net.ontopia.topicmaps.core.TopicMapBuilderIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.core.TopicMapStoreIF;
import net.ontopia.utils.FileUtils;
import net.ontopia.utils.PropertyUtils;
import net.ontopia.utils.TestFileUtils;
import net.ontopia.utils.URIUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.Before;
import org.junit.After;
public class SyncTest {
private Connection conn;
private Statement stm;
private TopicMapIF topicmap;
private RelationMapping mapping;
private final static String testdataDirectory = "db2tm";
@Before
public void setUp() throws SQLException, IOException {
// get JDBC connection to database
conn = getConnection();
// create tables
// (1) data table (id, name, type)
// (2) changes table
stm = conn.createStatement();
stm.executeUpdate("create table testdata (id integer, name varchar, type integer)");
stm.executeUpdate("create table testchanges (id integer, changetype integer, seq integer)");
stm.executeUpdate("create table complexdata (id1 integer, id2 integer, name varchar)");
stm.executeUpdate("create table complexchanges (id1 integer, id2 integer, changetype integer, seq integer)");
conn.commit();
// make empty TM
TopicMapStoreIF store = new InMemoryTopicMapStore();
LocatorIF baseloc = URIUtils.getURILocator("base:foo");
store.setBaseAddress(baseloc);
topicmap = store.getTopicMap();
}
@After
public void tearDown() throws SQLException, IOException {
// must close DB2TM's connection to the database, otherwise the drop
// table statements below will hang indefinitely
if (mapping != null)
mapping.close();
// sometimes connnections get into an error state. reopening to avoid
// this problem.
stm.close();
conn.close();
conn = getConnection();
stm = conn.createStatement();
// next three lines cause JDBC driver to hang
stm.executeUpdate("drop table testdata");
stm.executeUpdate("drop table testchanges");
stm.executeUpdate("drop table complexdata");
stm.executeUpdate("drop table complexchanges");
// close everything
conn.commit();
stm.close();
conn.close();
}
// --- Tests
/**
* Tests just a single UPDATE.
*/
@Test
public void testUpdate() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into testdata values (1, 'Topic', 2)");
conn.commit();
// read data into TM
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/SyncTest-mapping.xml");
Processor.addRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
// update data set
stm.executeUpdate("update testdata set type = 3 where id = 1");
stm.executeUpdate("insert into testchanges values (1, 0, 1)");
conn.commit();
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
exportTopicMap("sync-change-type");
}
/**
* Tests UPDATEs to more than one topic.
*/
@Test
public void testMultipleUpdates() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into testdata values (1, 'Topic 1', 2)");
stm.executeUpdate("insert into testdata values (2, 'Topic 2', 2)");
stm.executeUpdate("insert into testdata values (3, 'Topic 3', 2)");
conn.commit();
// read data into TM
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/SyncTest-mapping.xml");
Processor.addRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
// update data set
stm.executeUpdate("update testdata set type = 1 where id = 1");
stm.executeUpdate("update testdata set type = 2 where id = 2");
stm.executeUpdate("update testdata set type = 3 where id = 3");
stm.executeUpdate("insert into testchanges values (1, 0, 1)");
stm.executeUpdate("insert into testchanges values (2, 0, 2)");
stm.executeUpdate("insert into testchanges values (3, 0, 3)");
conn.commit();
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
exportTopicMap("multiple-updates");
}
/**
* Tests first a DELETE, then an INSERT.
*/
@Test
public void testDeleteInsert() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into testdata values (1, 'Topic', 2)");
conn.commit();
// read data into TM
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/SyncTest-mapping.xml");
Processor.addRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
// update data set
stm.executeUpdate("delete from testdata where id = 1");
stm.executeUpdate("insert into testchanges values (1, -1, 1)");
conn.commit();
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
topicmap.getStore().commit();
// update data set
stm.executeUpdate("insert into testdata values (1, 'Topic', 3)");
stm.executeUpdate("insert into testchanges values (1, 1, 2)");
conn.commit();
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
exportTopicMap("sync-change-type2");
}
/**
* Tests an UPDATE described as first a DELETE, then a CREATE. This
* tests for bug #2178.
*/
@Test
public void testDeleteCreate() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into testdata values (1, 'Topic', 2)");
conn.commit();
// read data into TM
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/SyncTest-mapping.xml");
Processor.addRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
// update data set
stm.executeUpdate("update testdata set type = 3 where id = 1");
stm.executeUpdate("insert into testchanges values (1, -1, 1)");
stm.executeUpdate("insert into testchanges values (1, 1, 2)");
conn.commit();
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
exportTopicMap("sync-change-type3");
}
/**
* Tests an IGNORE value. So nothing should happen.
*/
@Test
public void testIgnore() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into testdata values (1, 'Topic', 2)");
conn.commit();
// read data into TM
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/SyncTest-mapping.xml");
Processor.addRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
// update data set
stm.executeUpdate("insert into testchanges values (1, 2, 1)");
conn.commit();
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
exportTopicMap("ignore");
}
/**
* Tests situation when no changes have actually been made.
*/
@Test
public void testNochanges() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into testdata values (1, 'Topic', 2)");
conn.commit();
// read data into TM
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/SyncTest-mapping.xml");
Processor.addRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
// do not insert any changes
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
exportTopicMap("nochanges");
}
/**
* Tests just a single UPDATE on a compound key.
*/
@Test
public void testCompoundKey() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into complexdata values (1, 2, 'Topic')");
conn.commit();
// read data into TM
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/SyncTest-mapping.xml");
Processor.addRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
// update data set
stm.executeUpdate("update complexdata set name = 'Changed' where id1 = 1 and id2 = 2");
stm.executeUpdate("insert into complexchanges values (1, 2, 0, 1)");
conn.commit();
// sync TM with database
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
exportTopicMap("compound-key");
}
/**
* Tests for loss of types (as in issue 193).
*/
@Test
public void testTypeLoss() throws SQLException, IOException {
// create initial data set
stm.executeUpdate("insert into testdata values (1, 'Topic', 2)");
conn.commit();
// create a topic for the topic type, and give it a topic type
TopicMapBuilderIF builder = topicmap.getBuilder();
TopicIF topictype = builder.makeTopic();
TopicIF t2 = builder.makeTopic();
URILocator psi = new URILocator("psi:test/2");
t2.addSubjectIdentifier(psi); // will cause test data to match it
// verify that t2 has a topic type
t2.addType(topictype);
Assert.assertTrue("t2 has no topic type", !t2.getTypes().isEmpty());
TopicIF t1 = topicmap.getTopicBySubjectIdentifier(URIUtils.getURILocator("http://example.org/test/1"));
Assert.assertTrue("t1 was found", t1 == null);
// synchronize
mapping = RelationMapping.readFromClasspath("net/ontopia/topicmaps/db2tm/association-mapping.xml");
Processor.synchronizeRelations(mapping, null, topicmap,
topicmap.getStore().getBaseAddress());
t1 = topicmap.getTopicBySubjectIdentifier(URIUtils.getURILocator("http://example.org/test/1"));
Assert.assertTrue("t1 was not found", t1 != null);
Assert.assertTrue("t1 did not have t2 as its type", t1.getTypes().size() == 1 && t1.getTypes().contains(t2));
// t2 should not have lost its topic type in the sync!
Assert.assertTrue("t2 lost its topic type", !t2.getTypes().isEmpty());
}
// --- Internal methods
private Connection getConnection() throws SQLException, IOException {
Map<Object, Object> props = PropertyUtils.loadPropertiesFromClassPath("db2tm.h2.props");
props.put("net.ontopia.topicmaps.impl.rdbms.ConnectionPool", "false");
DefaultConnectionFactory cf = new DefaultConnectionFactory(props, false);
return cf.requestConnection();
}
private void exportTopicMap(String name) throws IOException {
File cxtm = TestFileUtils.getTestOutputFile(testdataDirectory, "out", name + ".cxtm");
String baseline = TestFileUtils.getTestInputFile(testdataDirectory, "baseline", name + ".cxtm");
// Export the result topic map to cxtm
FileOutputStream out = new FileOutputStream(cxtm);
(new CanonicalXTMWriter(out)).write(topicmap);
out.close();
// Check that the cxtm output matches the baseline.
Assert.assertTrue("The canonicalized conversion from " + name
+ " does not match the baseline: " + cxtm + " != " +
baseline,
FileUtils.compareFileToResource(cxtm, baseline));
}
}