| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.jackrabbit.oak.jcr; |
| |
| import static javax.jcr.observation.Event.PROPERTY_CHANGED; |
| import static org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl.REFRESH_INTERVAL; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectOutputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.jcr.Credentials; |
| import javax.jcr.GuestCredentials; |
| import javax.jcr.InvalidItemStateException; |
| import javax.jcr.ItemExistsException; |
| import javax.jcr.Node; |
| import javax.jcr.PropertyType; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.SimpleCredentials; |
| import javax.jcr.Value; |
| import javax.jcr.ValueFactory; |
| import javax.jcr.nodetype.ConstraintViolationException; |
| import javax.jcr.nodetype.NodeDefinition; |
| import javax.jcr.nodetype.NodeDefinitionTemplate; |
| import javax.jcr.nodetype.NodeTypeManager; |
| import javax.jcr.nodetype.NodeTypeTemplate; |
| import javax.jcr.nodetype.PropertyDefinition; |
| import javax.jcr.nodetype.PropertyDefinitionTemplate; |
| import javax.jcr.observation.Event; |
| import javax.jcr.observation.EventIterator; |
| import javax.jcr.observation.EventListener; |
| import javax.jcr.observation.ObservationManager; |
| import javax.jcr.query.Query; |
| import javax.jcr.query.QueryManager; |
| import javax.jcr.query.QueryResult; |
| import javax.jcr.query.RowIterator; |
| import javax.jcr.security.AccessControlManager; |
| import javax.jcr.security.Privilege; |
| |
| import junit.framework.Assert; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.jackrabbit.api.JackrabbitRepository; |
| import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; |
| import org.apache.jackrabbit.commons.JcrUtils; |
| import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; |
| import org.apache.jackrabbit.oak.api.CommitFailedException; |
| import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; |
| import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| /** |
| * This class contains test cases which demonstrate changes in behaviour wrt. to Jackrabbit 2. |
| * |
| * @see <a href="https://issues.apache.org/jira/browse/OAK-14">OAK-14: Identify and document changes in behaviour wrt. Jackrabbit 2</a> |
| */ |
| @RunWith(Parameterized.class) |
| public class CompatibilityIssuesTest extends AbstractRepositoryTest { |
| |
| public CompatibilityIssuesTest(NodeStoreFixture fixture) { |
| super(fixture); |
| } |
| |
| /** |
| * Trans-session isolation differs from Jackrabbit 2. Snapshot isolation can |
| * result in write skew as this test demonstrates: the check method enforces |
| * an application logic constraint which says that the sum of the properties |
| * p1 and p2 must not be negative. While session1 and session2 each enforce |
| * this constraint before saving, the constraint might not hold globally as |
| * can be seen in session3. |
| * |
| * @see <a href="http://wiki.apache.org/jackrabbit/Transactional%20model%20of%20the%20Microkernel%20based%20Jackrabbit%20prototype"> |
| * Transactional model of the Microkernel based Jackrabbit prototype</a> |
| */ |
| @Test |
| public void sessionIsolation() throws RepositoryException, ExecutionException, InterruptedException { |
| // Execute all operations in serial but on different threads to ensure |
| // same thread session refreshing doesn't come into the way |
| final Session session0 = createAdminSession(); |
| try { |
| run(new Callable<Void>() { |
| @Override |
| public Void call() throws Exception { |
| Node testNode = session0.getNode("/").addNode("testNode"); |
| testNode.setProperty("p1", 1); |
| testNode.setProperty("p2", 1); |
| session0.save(); |
| check(getAdminSession()); |
| return null; |
| } |
| }); |
| } finally { |
| session0.logout(); |
| } |
| |
| final Session session1 = createAdminSession(); |
| final Session session2 = createAdminSession(); |
| try { |
| run(new Callable<Void>() { |
| @Override |
| public Void call() throws Exception { |
| session1.getNode("/testNode").setProperty("p1", -1); |
| check(session1); |
| session1.save(); |
| return null; |
| } |
| }); |
| |
| run(new Callable<Void>() { |
| @Override |
| public Void call() throws Exception { |
| session2.getNode("/testNode").setProperty("p2", -1); |
| check(session2); // Throws on JR2, not on Oak |
| session2.save(); |
| return null; |
| } |
| }); |
| } finally { |
| session1.logout(); |
| session2.logout(); |
| } |
| |
| Session session3 = createAnonymousSession(); |
| try { |
| check(session3); // Throws on Oak |
| fail(); |
| } catch (AssertionError e) { |
| // expected |
| } finally { |
| session3.logout(); |
| } |
| } |
| |
| private static void run(Callable<Void> callable) throws InterruptedException, ExecutionException { |
| FutureTask<Void> task = new FutureTask<Void>(callable); |
| new Thread(task).start(); |
| task.get(); |
| } |
| |
| private static void check(Session session) throws RepositoryException { |
| if (session.getNode("/testNode").getProperty("p1").getLong() + |
| session.getNode("/testNode").getProperty("p2").getLong() < 0) { |
| fail("p1 + p2 < 0"); |
| } |
| } |
| |
| @Test |
| public void move() throws RepositoryException { |
| Session session = getAdminSession(); |
| |
| Node node = session.getNode("/"); |
| node.addNode("source").addNode("node"); |
| node.addNode("target"); |
| session.save(); |
| |
| session.refresh(true); |
| Node sourceNode = session.getNode("/source/node"); |
| session.move("/source/node", "/target/moved"); |
| // assertEquals("/target/moved", sourceNode.getPath()); // passes on JR2, fails on Oak |
| try { |
| sourceNode.getPath(); |
| } |
| catch (InvalidItemStateException expected) { |
| // sourceNode is stale |
| } |
| } |
| |
| /** |
| * Type checks are deferred to the Session#save call instead of the |
| * Node#addNode method like in Jackrabbit2. |
| * <p>Stacktrace in JR2:</p> |
| * <pre> |
| * {@code |
| * javax.jcr.nodetype.ConstraintViolationException: No child node definition for fail found in node /f1362578560413 |
| * at org.apache.jackrabbit.core.NodeImpl.addNode(NodeImpl.java:1276) |
| * at org.apache.jackrabbit.core.session.AddNodeOperation.perform(AddNodeOperation.java:111) |
| * at org.apache.jackrabbit.core.session.AddNodeOperation.perform(AddNodeOperation.java:1) |
| * at org.apache.jackrabbit.core.session.SessionState.perform(SessionState.java:216) |
| * at org.apache.jackrabbit.core.ItemImpl.perform(ItemImpl.java:91) |
| * at org.apache.jackrabbit.core.NodeImpl.addNodeWithUuid(NodeImpl.java:1814) |
| * at org.apache.jackrabbit.core.NodeImpl.addNode(NodeImpl.java:1774) |
| * } |
| * <pre> |
| * <p>Stacktrace in Oak:</p> |
| * <pre> |
| * {@code |
| *javax.jcr.nodetype.ConstraintViolationException |
| * at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) |
| * at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) |
| * at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) |
| * at java.lang.reflect.Constructor.newInstance(Constructor.java:513) |
| * at org.apache.jackrabbit.oak.api.CommitFailedException.throwRepositoryException(CommitFailedException.java:57) |
| * at org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegate.java:258) |
| * at org.apache.jackrabbit.oak.jcr.session.SessionImpl.save(SessionImpl.java:277) |
| * ... |
| *Caused by: org.apache.jackrabbit.oak.api.CommitFailedException: Cannot add node 'f1362578685631' at / |
| * at org.apache.jackrabbit.oak.plugins.nodetype.TypeValidator.childNodeAdded(TypeValidator.java:128) |
| * at org.apache.jackrabbit.oak.spi.commit.CompositeValidator.childNodeAdded(CompositeValidator.java:68) |
| * at org.apache.jackrabbit.oak.spi.commit.ValidatingHook$ValidatorDiff.childNodeAdded(ValidatingHook.java:159) |
| * at org.apache.jackrabbit.oak.core.RootImpl.commit(RootImpl.java:250) |
| * at org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegate.java:255) |
| * ... |
| *Caused by: javax.jcr.nodetype.ConstraintViolationException: Node 'jcr:content' in 'nt:file' is mandatory |
| * at org.apache.jackrabbit.oak.plugins.nodetype.EffectiveNodeTypeImpl.checkMandatoryItems(EffectiveNodeTypeImpl.java:288) |
| * at org.apache.jackrabbit.oak.plugins.nodetype.TypeValidator.childNodeAdded(TypeValidator.java:125) |
| * ... |
| * } |
| * <pre> |
| */ |
| @Test(expected = ConstraintViolationException.class) |
| public void typeChecksOnSave() throws RepositoryException { |
| Session session = getAdminSession(); |
| Node f = session.getNode("/").addNode("f" + System.currentTimeMillis(), "nt:file"); |
| f.addNode("fail", "nt:unstructured"); // this is where JR2 throws ConstraintViolationException |
| session.save(); // // this is where OAK throws ConstraintViolationException |
| } |
| |
| |
| /** |
| * OAK-939 - Change in behaviour from JR2. Following test case leads to |
| * CommitFailedException but it passes in JR2 |
| */ |
| @Test |
| public void removeNodeInDifferentSession() throws Throwable { |
| final String testNode = "test_node"; |
| final String testNodePath = '/' + testNode; |
| |
| // Create the test node |
| Session session = getAdminSession(); |
| session.getRootNode().addNode(testNode); |
| session.save(); |
| |
| // Test case would pass if the sessionRefreshInterval is set to zero |
| boolean refreshIntervalZero = false; |
| Session s3 = newSession(refreshIntervalZero); |
| Session s2 = newSession(refreshIntervalZero); |
| |
| s2.getNode(testNodePath).setProperty("foo", "bar"); |
| s2.save(); |
| |
| s3.getNode(testNodePath).remove(); |
| try { |
| s3.save(); |
| } catch (InvalidItemStateException e) { |
| assertTrue(e.getCause() instanceof CommitFailedException); |
| } |
| s2.logout(); |
| s3.logout(); |
| } |
| |
| private Session newSession(boolean refreshIntervalZero) throws RepositoryException { |
| Credentials creds = new SimpleCredentials("admin", "admin".toCharArray()); |
| if (refreshIntervalZero){ |
| return ((JackrabbitRepository) getRepository()) |
| .login(creds, null, Collections.<String, Object>singletonMap(REFRESH_INTERVAL, 0)); |
| } else{ |
| return getRepository().login(creds); |
| } |
| } |
| |
| /** |
| * OAK-948 - JR2 generates propertyChange event for touched properties while Oak does not |
| */ |
| @Test |
| public void noEventsForTouchedProperties() throws RepositoryException, InterruptedException { |
| final String testNodeName = "test_touched_node"; |
| final String testNodePath = '/' + testNodeName; |
| |
| // Create the test node |
| Session session = getAdminSession(); |
| Node testNode = session.getRootNode().addNode(testNodeName); |
| testNode.setProperty("foo", "bar"); |
| testNode.setProperty("foo2", "bar0"); |
| session.save(); |
| |
| Session observingSession = createAdminSession(); |
| ObservationManager observationManager = observingSession.getWorkspace().getObservationManager(); |
| |
| try{ |
| final List<Event> events = new ArrayList<Event>(); |
| final CountDownLatch latch = new CountDownLatch(1); |
| EventListener listener = new EventListener() { |
| @Override |
| public void onEvent(EventIterator eventIt) { |
| while(eventIt.hasNext()){ |
| events.add(eventIt.nextEvent()); |
| } |
| if(!events.isEmpty()){ |
| latch.countDown(); |
| } |
| } |
| }; |
| |
| observationManager.addEventListener(listener, PROPERTY_CHANGED, testNodePath, true, null, null, false); |
| |
| //Now touch foo and modify foo2 |
| session.getNode(testNodePath).setProperty("foo","bar"); |
| session.getNode(testNodePath).setProperty("foo2","bar2"); |
| session.save(); |
| |
| latch.await(60, TimeUnit.SECONDS); |
| |
| //Only one event is recorded for foo2 modification |
| assertEquals(1,events.size()); |
| }finally{ |
| observingSession.logout(); |
| } |
| } |
| |
| @Test |
| public void noSNSSupport() throws RepositoryException{ |
| Session session = getAdminSession(); |
| Node testNode = session.getRootNode().addNode("test", "nt:unstructured"); |
| session.save(); |
| |
| testNode.addNode("foo"); |
| try { |
| testNode.addNode("foo"); |
| // This would fail on JR2 since there SNSs are supported |
| fail("Expected ItemExistsException"); |
| } catch (ItemExistsException e){ |
| //ItemExistsException is expected to be thrown |
| } |
| session.save(); |
| try { |
| testNode.addNode("foo"); |
| // This would fail on JR2 since there SNSs are supported |
| fail("Expected ItemExistsException"); |
| } catch (ItemExistsException e){ |
| //ItemExistsException is expected to be thrown |
| } |
| } |
| |
| @Test |
| public void addNodeTest() throws RepositoryException { |
| Session session = getAdminSession(); |
| |
| // node type with default child-node type of to nt:base |
| String ntName = "test"; |
| NodeTypeManager ntm = session.getWorkspace().getNodeTypeManager(); |
| NodeTypeTemplate ntt = ntm.createNodeTypeTemplate(); |
| ntt.setName(ntName); |
| NodeDefinitionTemplate child = ntm.createNodeDefinitionTemplate(); |
| child.setName("*"); |
| child.setDefaultPrimaryTypeName("nt:base"); |
| child.setRequiredPrimaryTypeNames(new String[] {"nt:base"}); |
| List<NodeDefinition> children = ntt.getNodeDefinitionTemplates(); |
| children.add(child); |
| ntm.registerNodeType(ntt, true); |
| |
| // try to create a node with the default nt:base |
| Node node = session.getRootNode().addNode("defaultNtBase", ntName); |
| node.addNode("nothrow"); // See OAK-1013 |
| try { |
| node.addNode("throw", "nt:hierarchyNode"); |
| fail("Abstract primary type should cause ConstraintViolationException"); |
| } catch (ConstraintViolationException expected) { |
| } |
| session.save(); |
| } |
| |
| @Test |
| public void testBinaryCoercion() throws RepositoryException, IOException { |
| Session session = getAdminSession(); |
| |
| // node type with default child-node type of to nt:base |
| String ntName = "binaryCoercionTest"; |
| NodeTypeManager ntm = session.getWorkspace().getNodeTypeManager(); |
| |
| NodeTypeTemplate ntt = ntm.createNodeTypeTemplate(); |
| ntt.setName(ntName); |
| |
| PropertyDefinitionTemplate propertyWithType = ntm.createPropertyDefinitionTemplate(); |
| propertyWithType.setName("javaObject"); |
| propertyWithType.setRequiredType(PropertyType.STRING); |
| |
| PropertyDefinitionTemplate unnamed = ntm.createPropertyDefinitionTemplate(); |
| unnamed.setName("*"); |
| unnamed.setRequiredType(PropertyType.UNDEFINED); |
| |
| List<PropertyDefinition> properties = ntt.getPropertyDefinitionTemplates(); |
| properties.add(propertyWithType); |
| properties.add(unnamed); |
| |
| ntm.registerNodeType(ntt, false); |
| |
| Node node = session.getRootNode().addNode("testNodeForBinary", ntName); |
| ByteArrayOutputStream bos = serializeObject("testValue"); |
| node.setProperty("javaObject",session.getValueFactory().createBinary(new ByteArrayInputStream(bos.toByteArray()))); |
| |
| Assert.assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(bos.toByteArray()), node.getProperty("javaObject").getStream())); |
| } |
| |
| private ByteArrayOutputStream serializeObject(Object o) throws IOException { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(5000); |
| ObjectOutputStream objectStream = new ObjectOutputStream(out); |
| objectStream.writeObject(o); |
| objectStream.flush(); |
| return out; |
| } |
| |
| @Test |
| public void testSearchDescendentUsingXPath() throws Exception { |
| Session adminSession = getAdminSession(); |
| String testNodePath = "/home/users/geometrixx-outdoors/emily.andrews@mailinator.com/social/relationships/following/aaron.mcdonald@mailinator.com"; |
| Node testNode = JcrUtils.getOrCreateByPath(testNodePath, null, adminSession); |
| testNode.setProperty("id", "aaron.mcdonald@mailinator.com"); |
| |
| AccessControlManager acMgr = adminSession.getAccessControlManager(); |
| JackrabbitAccessControlList tmpl = AccessControlUtils.getAccessControlList(acMgr, "/home/users/geometrixx-outdoors"); |
| ValueFactory vf = adminSession.getValueFactory(); |
| Map<String, Value> restrictions = new HashMap<String, Value>(); |
| restrictions.put("rep:glob", vf.createValue("*/social/relationships/following/*")); |
| tmpl.addEntry(EveryonePrincipal.getInstance(), new Privilege[]{acMgr.privilegeFromName(Privilege.JCR_READ)}, true, restrictions); |
| acMgr.setPolicy(tmpl.getPath(), tmpl); |
| adminSession.save(); |
| |
| Session anonymousSession = getRepository().login(new GuestCredentials()); |
| QueryManager qm = anonymousSession.getWorkspace().getQueryManager(); |
| Query q = qm.createQuery("/jcr:root/home//social/relationships/following//*[@id='aaron.mcdonald@mailinator.com']", Query.XPATH); |
| QueryResult r = q.execute(); |
| RowIterator it = r.getRows(); |
| Assert.assertTrue(it.hasNext()); |
| anonymousSession.logout(); |
| } |
| |
| } |