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

    }

}

Reply via email to