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(); } } }