/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.io.IOUtils;
import org.eclipse.ecr.core.NXCore;
import org.eclipse.ecr.core.api.impl.blob.StringBlob;
import org.eclipse.ecr.core.event.Event;
import org.eclipse.ecr.core.event.EventContext;
import org.eclipse.ecr.core.storage.EventConstants;
import org.eclipse.ecr.core.storage.StorageException;
import org.eclipse.ecr.core.storage.sql.RepositoryDescriptor.ServerDescriptor;
import org.eclipse.ecr.core.storage.sql.coremodel.SQLRepository;
import org.eclipse.ecr.core.storage.sql.listeners.DummyTestListener;
import org.eclipse.ecr.core.storage.sql.testlib.DatabaseH2;
import org.eclipse.ecr.core.storage.sql.testlib.DatabaseHelper;
import org.eclipse.ecr.core.storage.sql.testlib.DatabasePostgreSQL;
/**
* Tests for NetBackend.
*/
public class TestSQLBackendNet extends TestSQLBackend {
public static final String TEST_BUNDLE = "org.eclipse.ecr.core.storage.sql.test";
public static final String TESTLIB_BUNDLE = "org.eclipse.ecr.core.storage.sql.testlib";
// defined in repo XML config as well
private static final String SERVER_REPO_NAME = "test";
private static final String CLIENT_REPO_NAME = "client";
private static final String CLIENT_REPO_NAME_2 = CLIENT_REPO_NAME + "2";
public static List<Event> EVENTS = DummyTestListener.EVENTS_RECEIVED;
protected String repoName;
protected ServerVCS serverVCS;
@Override
public void setUp() throws Exception {
repoName = CLIENT_REPO_NAME;
super.setUp();
deployContrib(TEST_BUNDLE,
"OSGI-INF/test-listeners-invalidations-contrib.xml");
deployRepositoryContrib();
serverVCS = new ServerVCS(SERVER_REPO_NAME);
serverVCS.start();
}
// config used by server
protected void deployRepositoryContrib() throws Exception {
if (DatabaseHelper.DATABASE instanceof DatabaseH2) {
String contrib = "OSGI-INF/test-server-h2-contrib.xml";
deployContrib(TESTLIB_BUNDLE, contrib);
} else if (DatabaseHelper.DATABASE instanceof DatabasePostgreSQL) {
String contrib = "OSGI-INF/test-server-postgresql-contrib.xml";
deployContrib(TESTLIB_BUNDLE, contrib);
} else {
deployContrib(TESTLIB_BUNDLE,
DatabaseHelper.DATABASE.getDeploymentContrib());
}
}
// descriptor used by client
@Override
protected RepositoryDescriptor newDescriptor(long clusteringDelay, boolean fulltextDisabled) {
RepositoryDescriptor descriptor = super.newDescriptor(clusteringDelay, fulltextDisabled);
descriptor.name = repoName;
descriptor.binaryStorePath = "clientbinaries";
descriptor.binaryManagerConnect = true;
ServerDescriptor sd = new ServerDescriptor();
sd.host = "localhost";
sd.port = 8181;
sd.path = "/nuxeo";
descriptor.connect = Collections.singletonList(sd);
descriptor.sendInvalidationEvents = true;
return descriptor;
}
@Override
public void tearDown() throws Exception {
closeRepository();
serverVCS.interrupt();
serverVCS.join();
super.tearDown();
}
public static class ServerVCS extends Thread {
protected final String repositoryName;
protected Repository repository;
protected final BlockingQueue<String> methodCall = new LinkedBlockingQueue<String>(1);
protected final BlockingQueue<Object> methodResult = new LinkedBlockingQueue<Object>(1);
public ServerVCS(String repositoryName) {
super("Nuxeo-VCS-Server-Test");
this.repositoryName = repositoryName;
}
@Override
public void start() {
super.start();
// wait until repository ready
serverCall("started");
}
public Object serverCall(String methodName) {
try {
methodCall.put(methodName);
Object res = methodResult.take();
if (res instanceof AssertionError) {
AssertionError e = (AssertionError) res;
throw (AssertionError) new AssertionError("Server Error: " + e).initCause(e);
}
return res;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
SQLRepository repo = null;
try {
// Looking up the model repository will call
// SQLRepositoryFactory.createRepository to create a
// SQLRepository which creates a RepositoryImpl,
// which is configured to spawn a server to listen for remote
// connections.
repo = (SQLRepository) NXCore.getRepositoryService().getRepositoryManager().getRepository(repositoryName);
// init root
repo.getSession(null).close();
repository = repo.repository;
// now process remote calls, until interrupted by caller
try {
while (true) {
String methodName = methodCall.take();
Object res;
try {
res = getClass().getMethod(methodName).invoke(this);
} catch (InvocationTargetException e) {
res = e.getCause();
}
methodResult.put(res == null ? Boolean.TRUE : res);
}
} catch (InterruptedException e) {
// restore interrupted status
interrupt();
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (repo != null) {
repo.shutdown();
}
}
}
// called from other thread via reflection: serverCall
public void started() {
}
// ************ testNetInvalidations *************
private Session session;
private SimpleProperty title;
private Node node;
// called from client thread via reflection: serverCall
public void testNetInvalidations_Init() throws Exception {
session = repository.getConnection();
Node root = session.getRootNode();
node = session.getChildNode(root, "foo", false);
title = node.getSimpleProperty("tst:title");
assertEquals("before", title.getString());
}
// called from client thread via reflection: serverCall
public void testNetInvalidations_Check() throws Exception {
// session has not saved (committed) yet, so still unchanged
assertEquals("before", title.getString());
assertEquals(1, EVENTS.size());
session.save();
// after save, remote invalidations have been processed
assertEquals("after", title.getString());
// remote inval from client to server
checkEvent(1, false, SERVER_REPO_NAME, node.getId());
}
// called from client thread via reflection: serverCall
public void testNetInvalidations_Change() throws Exception {
title.setValue("new");
assertEquals(2, EVENTS.size());
session.save();
// server self inval
checkEvent(2, true, SERVER_REPO_NAME, node.getId());
}
}
public void testClientServerInvalidations() throws Exception {
Session session = repository.getConnection();
Node root = session.getRootNode();
Node node = session.addChildNode(root, "foo", null, "TestDoc", false);
Serializable nodeId = node.getId();
SimpleProperty title = node.getSimpleProperty("tst:title");
title.setValue("before");
session.save();
assertEquals("before", title.getString());
DummyTestListener.clear();
serverVCS.serverCall("testNetInvalidations_Init");
// change title
title.setValue("after");
assertEquals(0, EVENTS.size());
// save session and queue its invalidations to others
// note that to be correct this has to also send the invalidations
// server-side
session.save();
assertEquals("after", title.getString());
// events received
// repo 1 self inval
checkEvent(0, true, CLIENT_REPO_NAME, nodeId);
serverVCS.serverCall("testNetInvalidations_Check");
// now change prop on the server
serverVCS.serverCall("testNetInvalidations_Change");
// check visible change after save
assertEquals("after", title.getString());
session.save();
assertEquals("new", title.getString());
// remote inval from server to client
checkEvent(3, false, CLIENT_REPO_NAME, nodeId);
}
// this test is similar to clustering tests
public void testTwoClientsInvalidations() throws Exception {
repoName = CLIENT_REPO_NAME_2;
repository2 = newRepository(-1, false);
Session session1 = repository.getConnection();
Node root1 = session1.getRootNode();
Node node1 = session1.addChildNode(root1, "foo", null, "TestDoc", false);
Serializable nodeId = node1.getId();
SimpleProperty title1 = node1.getSimpleProperty("tst:title");
title1.setValue("before");
session1.save();
assertEquals("before", title1.getString());
// check session 2 has before state
Session session2 = repository2.getConnection();
Node root2 = session2.getRootNode();
Node node2 = session2.getChildNode(root2, "foo", false);
SimpleProperty title2 = node2.getSimpleProperty("tst:title");
assertEquals("before", title2.getString());
DummyTestListener.clear();
// change title in session 1
title1.setValue("after");
// save session and queue its invalidations to others
// note that to be correct this has to also send the invalidations
// server-side
session1.save();
assertEquals("after", title1.getString());
// repo 1 self inval
checkEvent(0, true, CLIENT_REPO_NAME, nodeId);
// session2 has not saved (committed) yet, so still unchanged
assertEquals("before", title2.getString());
session2.save();
// after commit/save, invalidations have been processed
assertEquals("after", title2.getString());
// and event sent from repo 1 to repo 2
checkEvent(1, false, CLIENT_REPO_NAME_2, nodeId);
}
protected SimpleProperty getBlob(Session session) throws StorageException, IOException {
Node root = session.getRootNode();
Node node = session.addChildNode(root, "pff", null, "content", false);
SimpleProperty prop = node.getSimpleProperty("data");
assertNotNull(prop);
return prop;
}
public void testSerializeRepoBinaries() throws Exception {
BinaryManager binMgr = ((RepositoryImpl) repository).binaryManager;
StringBlob blob = new StringBlob("dummy");
Binary data = binMgr.getBinary(blob.getStream());
checkSerialization(data);
}
protected void checkSerialization(Binary data) throws IOException, ClassNotFoundException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(data);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Binary unmarshalled = (Binary) ois.readObject();
String unmarshalledString = IOUtils.toString(unmarshalled.getStream());
String originalString = IOUtils.toString(data.getStream());
assertEquals(unmarshalledString, originalString);
}
public void testSerializeDisconnectedBinaries() throws Exception {
File file = File.createTempFile("nuxeo-test-", ".blob");
file.deleteOnExit();
Binary data = new Binary(file, "abc");
checkSerialization(data);
}
protected static void checkEvent(int i, boolean local, String repo, Serializable id) {
assertTrue("size=" + EVENTS.size() + ", i=" + i, i < EVENTS.size());
assertEquals(i, EVENTS.size() - 1);
Event event = EVENTS.get(i);
assertEquals(EventConstants.EVENT_VCS_INVALIDATIONS, event.getName());
EventContext ctx = event.getContext();
assertEquals(repo, ctx.getRepositoryName());
@SuppressWarnings("unchecked")
Set<String> set = (Set<String>) ctx.getProperty(EventConstants.INVAL_MODIFIED_DOC_IDS);
assertEquals(Collections.singleton(id), set);
// NXP-5808 cannot distinguish local/cluster invalidations
// Boolean loc = (Boolean) ctx.getProperty(EventConstants.INVAL_LOCAL);
// assertEquals(Boolean.valueOf(local), loc);
}
}