On Thu, Oct 20, 2005 at 05:27:51PM +0200, Marcel Reutegger wrote:
> Because of JCRs data model this can potentially happen to any save()
> call, unless the modification is protected with a lock.
> when one session modifies a node and tries to save it, another session
> might have been faster and the changes of the latter session win. the
> first session then gets an InvalidItemStateException because it tried to
> save inconsistent data.
Okay, I understood this point. However, I found node should be set with
nodetype mix:lockable (?) to allow locks fo changes. But if I change node type
from nt:unstructured to mix:lockable, I'm getting exceptions:
javax.jcr.nodetype.ConstraintViolati
onException: no definition found in parent node's node type for new node: no
matching child node definition found for {}node1_1: no matching child node
definition found for{}node1_1
[java] javax.jcr.nodetype.ConstraintViolationException: no definition
found in parent node's node type for new node: no matching child node
definition found for{}node1_1: no matching child node definition found
for {}node1_1
Source code attached.
--
Eugene N Dzhurinsky
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.jcr.ItemExistsException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import javax.jcr.version.VersionException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.jackrabbit.core.jndi.RegistryHelper;
import org.apache.jackrabbit.core.value.BLOBFileValue;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class JCRTest {
/**
* Repository to operate on
*/
private static Repository repo = null;
/**
* Mutex object to synchronize
*/
private static final String mutex = "mutex";
/**
* Node path
*/
private static final String STATIC_NODE_PATH = "/node1_3/node2_3";
/**
* Number of nodes in memory, after which we need to flush repository
*/
private static final int SAVE_INTERVAL = 200;
/**
* Number of childs per node
*/
private static final int CHILD_NODE_COUNT = 3;
/**
* Root node name
*/
private static final String RANDOM_NODE_NAME = "random";
/**
* Root node type
*/
private static final String TEST_NODE_TYPE = "nt:unstructured";
/**
* Localable node type
*/
private static final String LOCKABLE_NODE_TYPE = "mix:lockable";
/**
* THREADS NUMBER
*/
private static final int THREAD_NUMBER = 50;
/**
* Timeout to wait until some thread finishes
*/
private static final int TIMEOUT = 50 * 1000;
private static final Logger log = Logger.getLogger(JCRTest.class);
private static final String configFile = "repotest/repository.xml";
private static final String repHomeDir = "repotest";
private Session session;
/**
* Date object to use for time consumption calculation
*/
Date d = null;
long totalNodesCreated = 0;
long buildTime = 0;
long traverseTime = 0;
long searchTime = 0;
long accessTime = 0;
/**
* number of references for Session objects
*/
int refcount;
public JCRTest() throws NamingException, RepositoryException {
session = createSession("");
refcount = 0;
}
/**
* Creates repository and performs some tests for time consumption on
* searching operations
*/
public void runTest() {
try {
Node rn = session.getRootNode();
log.debug("Creating random node");
Date start = new Date();
if (!rn.hasNode(RANDOM_NODE_NAME)) {
Node root = rn.addNode(RANDOM_NODE_NAME, TEST_NODE_TYPE);
buildNode(root, 1);
session.save();
}
Date stop = new Date();
buildTime = (stop.getTime() - start.getTime());
start = new Date();
/*
* if (rn.hasNode(RANDOM_NODE_NAME)) { log.debug("Traversing back");
* Node root = rn.getNode(RANDOM_NODE_NAME);
* log.debug(root.getPath()); traverseNode(root); }
*/
stop = new Date();
traverseTime = stop.getTime() - start.getTime();
start = new Date();
Node n = rn.getNode(RANDOM_NODE_NAME + STATIC_NODE_PATH);
stop = new Date();
accessTime = stop.getTime() - start.getTime();
dumpProperties(n);
String query = "//*[jcr:contains(@prop, '3_3_1')]";
log.debug(query);
Query q = session.getWorkspace().getQueryManager().createQuery(
query, javax.jcr.query.Query.XPATH);
start = new Date();
QueryResult queryResult = q.execute();
stop = new Date();
NodeIterator nodeIterator = queryResult.getNodes();
searchTime = stop.getTime() - start.getTime();
log.debug("Dump " + nodeIterator.getSize() + " results ");
while (nodeIterator.hasNext()) {
dumpProperties(nodeIterator.nextNode());
}
log
.debug("Build " + totalNodesCreated + " in " + buildTime
+ " ms");
log.debug("Traverse " + totalNodesCreated + " in " + traverseTime
+ " ms");
log.debug("Random access " + searchTime + " ms");
log.debug("Search " + searchTime + " ms");
} catch (Exception e) {
log.error(e, e);
}
}
/**
* Shows node's childs and properties
*/
private void traverseNode(Node root) throws RepositoryException {
if (root.hasNodes()) {
NodeIterator it = root.getNodes();
while (it.hasNext()) {
Node n = it.nextNode();
log.debug(n.getPath());
traverseNode(n);
}
}
}
/**
* Recursively duilds children for node
*/
private void buildNode(Node root, int level) throws ItemExistsException,
PathNotFoundException, NoSuchNodeTypeException, LockException,
VersionException, ConstraintViolationException,
RepositoryException, ValueFormatException,
UnsupportedRepositoryOperationException {
for (int i = 1; i <= CHILD_NODE_COUNT; i++) {
Node n = root.addNode("node" + level + "_" + i, LOCKABLE_NODE_TYPE);
if (++totalNodesCreated % SAVE_INTERVAL == 0) {
long time = -1;
if (d != null) {
Date current = new Date();
time = current.getTime() - d.getTime();
d = current;
} else
d = new Date();
log.debug("Saving session with " + totalNodesCreated
+ (time > 0 ? " in " + time + " ms" : ""));
session.save();
log.debug("Session saved");
}
n.setProperty("prop_blob", new BLOBFileValue(
(level + "_" + i + "_1").getBytes()));
n.setProperty("prop", session.getValueFactory().createValue(
level + "_" + i + "_1"));
if (level <= CHILD_NODE_COUNT)
buildNode(n, level + 1);
}
}
/**
* returns the session and increases internal counter for number of
* references for this object
*/
public Session getSession() {
++refcount;
return session;
}
/**
* decrements the reference counter and if it is equals to 0 - closes
* session
*/
public void releaseSession() throws Exception {
if (--refcount == 0) {
log.debug("Flushing session");
synchronized (mutex) {
session.save();
}
session.logout();
}
}
/**
* dumps properties of the node
*/
public void dumpProperties(Node n) throws RepositoryException {
log.info(n.getPath());
PropertyIterator pit = n.getProperties();
while (pit.hasNext()) {
Property p = pit.nextProperty();
StringBuffer b = new StringBuffer(p.getPath() + "=");
if (p.getDefinition().isMultiple()) {
Value[] values = p.getValues();
for (int i = 0; i < values.length; i++) {
b.append(values[i].getString());
if (i < values.length - 1)
b.append(',');
}
} else {
b.append(p.getString());
}
log.info(b);
}
}
/**
* Creates session object for given username
*/
private Session createSession(String username) throws NamingException,
RepositoryException {
if (repo == null) {
Hashtable env = new Hashtable();
env
.put(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory");
env.put(Context.PROVIDER_URL, "localhost");
InitialContext ctx = new InitialContext(env);
RegistryHelper.registerRepository(ctx, "repo", configFile,
repHomeDir, true);
repo = (Repository) ctx.lookup("repo");
}
return repo.login(new SimpleCredentials(username, "".toCharArray()));
}
/**
* Main method
*/
public static void main(String[] args) throws Exception {
PropertyConfigurator.configure("log4j.properties");
JCRTest test = new JCRTest();
test.runTest();
ThreadContainer cont = new ThreadContainer();
testSessionsForUser(test, cont);
cont.start();
cont.waitForRelease();
log.debug("Exitting with thread " + cont.getLastThread());
dumpResults(test, cont);
for (int i = 0; i < THREAD_NUMBER; i++) {
test = new JCRTest();
testSessionsForUser(test, cont);
}
cont.start();
log.debug("Waiting for threads");
cont.waitForRelease();
log.debug("Exitting with thread " + cont.getLastThread());
test = new JCRTest();
dumpResults(test, cont);
}
/**
* Dumps the results after multi-threaded testing
*/
private static void dumpResults(JCRTest test, ThreadContainer cont) {
try {
Session session = test.createSession("");
Node root = session.getRootNode();
Node r = root.getNode(RANDOM_NODE_NAME + STATIC_NODE_PATH);
test.dumpProperties(r);
log.debug("Property is "
+ r.getProperty("test").getValue().getString() + " == "
+ cont.getLastThread());
session.logout();
} catch (Exception e) {
log.error(e, e);
}
}
/**
* Creates set of threads to be executed for single "user"
*/
private static void testSessionsForUser(JCRTest test, ThreadContainer cont) {
for (int i = 0; i < THREAD_NUMBER; i++)
new SingleSessionAccessThread(cont, test, STATIC_NODE_PATH, String
.valueOf(i));
}
/**
* Emulates single access tot the same property within same session
*/
static class SingleSessionAccessThread extends Thread {
private JCRTest test;
private Session session;
private String nodePath;
private String propertyValue;
private ThreadContainer container;
public SingleSessionAccessThread(ThreadContainer container,
JCRTest test, String nodePath, String propertyValue) {
super(propertyValue);
this.container = container;
this.test = test;
this.nodePath = nodePath;
this.propertyValue = propertyValue;
this.session = test.getSession();
container.add(this);
}
public void run() {
try {
Node root = session.getRootNode();
sleep(Math.round(Math.random() * 1000));
log
.debug("Setting property for " + RANDOM_NODE_NAME
+ nodePath);
Node current = root.getNode(RANDOM_NODE_NAME + nodePath);
current.lock(true, true);
current.setProperty("test", propertyValue);
current.unlock();
} catch (RepositoryException e) {
log.error(e, e);
} catch (InterruptedException e) {
log.error(e, e);
} finally {
container.remove(this);
try {
test.releaseSession();
} catch (Exception e) {
log.error(e, e);
}
}
}
}
/**
* Collects threads and waits for all threads to be finished
*/
static class ThreadContainer {
private List threads;
private String lastThread;
public ThreadContainer() {
threads = new LinkedList();
}
public synchronized void add(SingleSessionAccessThread obj) {
threads.add(obj);
}
public synchronized void remove(SingleSessionAccessThread obj) {
threads.remove(obj);
lastThread = obj.getName();
notifyAll();
}
public synchronized void waitForRelease() {
try {
while (!threads.isEmpty())
wait(TIMEOUT);
} catch (InterruptedException e) {
log.error(e);
}
}
/**
* @return Returns the lastThread.
*/
public String getLastThread() {
return lastThread;
}
public synchronized void start() {
for (Iterator it = threads.iterator(); it.hasNext();)
((Thread) it.next()).start();
}
}
}