| /* |
| * 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.sling.ide.impl.vlt; |
| |
| import static org.apache.sling.ide.transport.Repository.CommandExecutionFlag.CREATE_ONLY_WHEN_MISSING; |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.junit.Assert.assertThat; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.util.concurrent.Callable; |
| |
| import javax.jcr.Credentials; |
| import javax.jcr.Node; |
| import javax.jcr.Property; |
| 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.nodetype.InvalidNodeTypeDefinitionException; |
| import javax.jcr.nodetype.NodeType; |
| import javax.jcr.nodetype.NodeTypeExistsException; |
| |
| import org.apache.jackrabbit.commons.cnd.CndImporter; |
| import org.apache.jackrabbit.commons.cnd.ParseException; |
| import org.apache.jackrabbit.core.TransientRepository; |
| import org.apache.sling.ide.filter.Filter; |
| import org.apache.sling.ide.filter.FilterResult; |
| import org.apache.sling.ide.log.Logger; |
| import org.apache.sling.ide.transport.CommandContext; |
| import org.apache.sling.ide.transport.ResourceProxy; |
| import org.hamcrest.Matchers; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| |
| public class AddOrUpdateNodeCommandTest { |
| |
| private static final CommandContext DEFAULT_CONTEXT = new CommandContext(new Filter() { |
| @Override |
| public FilterResult filter(String repositoryPath) { |
| return FilterResult.ALLOW; |
| } |
| }); |
| |
| private static final String PROP_NAME = "jcr:title"; |
| |
| private Logger logger = new Slf4jLogger(); |
| |
| @Test |
| public void setProperty() throws Exception { |
| |
| doPropertyChangeTest(null, "Title"); |
| } |
| |
| private void doPropertyChangeTest(final Object initialPropertyValues, final Object newPropertyValues) |
| throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| Node contentNode = session().getRootNode().addNode("content"); |
| if (initialPropertyValues instanceof String) { |
| contentNode.setProperty(PROP_NAME, (String) initialPropertyValues); |
| } else if (initialPropertyValues instanceof String[]) { |
| contentNode.setProperty(PROP_NAME, (String[]) initialPropertyValues); |
| } |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", NodeType.NT_UNSTRUCTURED); |
| if (newPropertyValues != null) { |
| resource.addProperty(PROP_NAME, newPropertyValues); |
| } |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| if (newPropertyValues == null) { |
| assertThat(session().getNode("/content").hasProperty(PROP_NAME), equalTo(false)); |
| return null; |
| } |
| |
| Property newProp = session().getNode("/content").getProperty(PROP_NAME); |
| if (newPropertyValues instanceof String) { |
| assertThat("property.isMultiple", newProp.isMultiple(), equalTo(Boolean.FALSE)); |
| assertThat(newProp.getString(), equalTo((String) newPropertyValues)); |
| |
| } else { |
| |
| String[] expectedValues = (String[]) newPropertyValues; |
| assertThat("property.isMultiple", newProp.isMultiple(), equalTo(Boolean.TRUE)); |
| |
| Value[] values = session().getNode("/content").getProperty(PROP_NAME).getValues(); |
| |
| assertThat(values.length, equalTo(expectedValues.length)); |
| for (int i = 0; i < values.length; i++) { |
| assertThat(values[i].getString(), equalTo(expectedValues[i])); |
| } |
| |
| } |
| |
| return null; |
| } |
| }); |
| |
| } |
| |
| @Test |
| public void removeProperty() throws Exception { |
| |
| doPropertyChangeTest("Title", null); |
| } |
| |
| @Test |
| public void singlePropertyToMultiValued() throws Exception { |
| |
| doPropertyChangeTest("Title", new String[] { "Title", "Title 2" }); |
| } |
| |
| @Test |
| public void multiValuesPropertyToSingle() throws Exception { |
| |
| doPropertyChangeTest(new String[] { "Title", "Title 2" }, "Title"); |
| } |
| |
| @Test |
| public void changeNtFolderToSlingFolderWithAddedProperty() throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| session().getRootNode().addNode("content", "nt:folder"); |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", "sling:Folder"); |
| resource.getProperties().put("newProperty", "some/value"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| Node content = session().getRootNode().getNode("content"); |
| assertThat(content.getPrimaryNodeType().getName(), equalTo("sling:Folder")); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void changeSlingFolderToNtFolderWithExistingProperty() throws Exception { |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| Node content = session().getRootNode().addNode("content", "sling:Folder"); |
| content.setProperty("newProperty", "some/value"); |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", "nt:folder"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| content = session().getRootNode().getNode("content"); |
| assertThat(content.getPrimaryNodeType().getName(), equalTo("nt:folder")); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| @Ignore("SLING-4036") |
| public void updateNtUnstructuredToNodeWithRequiredProperty() throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| Node content = session().getRootNode().addNode("content", "nt:unstructured"); |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", "custom"); |
| resource.getProperties().put("attribute", "some value"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| content = session().getRootNode().getNode("content"); |
| assertThat(content.getPrimaryNodeType().getName(), equalTo("custom")); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void nodeNotPresentButOutsideOfFilterIsNotRemoved() throws Exception { |
| |
| final CommandContext context = new CommandContext(new Filter() { |
| |
| @Override |
| public FilterResult filter(String repositoryPath) { |
| if ( repositoryPath.equals("/content/not-included-child")) { |
| return FilterResult.DENY; |
| } |
| |
| return FilterResult.ALLOW; |
| } |
| }); |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| Node content = session().getRootNode().addNode("content", "nt:unstructured"); |
| content.addNode("included-child"); |
| content.addNode("not-included-child"); |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", "nt:unstructured"); |
| resource.addChild(newResource("/content/included-child", "nt:unstructured")); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), context, null, resource, logger); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| content = session().getRootNode().getNode("content"); |
| content.getNode("included-child"); |
| content.getNode("not-included-child"); |
| return null; |
| } |
| }); |
| |
| } |
| |
| private ResourceProxy newResource(String path, String primaryType) { |
| |
| ResourceProxy resource = new ResourceProxy(path); |
| resource.addProperty("jcr:primaryType", primaryType); |
| return resource; |
| } |
| |
| @Test |
| public void createIfRequiredFlagSkipsExistingResources() throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| Node content = session().getRootNode().addNode("content", "nt:folder"); |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", "nt:unstructured"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger, |
| CREATE_ONLY_WHEN_MISSING); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| content = session().getRootNode().getNode("content"); |
| assertThat(content.getPrimaryNodeType().getName(), equalTo("nt:folder")); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void createIfRequiredFlagCreatesNeededResources() throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| ResourceProxy resource = newResource("/content", "nt:unstructured"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger, |
| CREATE_ONLY_WHEN_MISSING); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| Node content = session().getRootNode().getNode("content"); |
| assertThat(content.getPrimaryNodeType().getName(), equalTo("nt:unstructured")); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void createIfRequiredFlagCreatesNeededResourcesEvenWhenPrimaryTypeIsMissing() throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| ResourceProxy resource = new ResourceProxy("/content"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger, |
| CREATE_ONLY_WHEN_MISSING); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| Node content = session().getRootNode().getNode("content"); |
| assertThat(content.getPrimaryNodeType().getName(), equalTo("nt:unstructured")); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void autoCreatedPropertiesAreNotRemoved() throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| Node content = session().getRootNode().addNode("content", "nt:folder"); |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", "nt:folder"); |
| resource.addProperty("jcr:mixinTypes", "mix:lastModified"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger); |
| cmd.execute().get(); |
| cmd.execute().get(); // second time since mixins are processed after properties so we need two |
| // executions to |
| // expose the problem |
| |
| session().refresh(false); |
| |
| content = session().getRootNode().getNode("content"); |
| assertThat("jcr:lastModified property not present", content.hasProperty("jcr:lastModified"), |
| equalTo(true)); |
| assertThat("jcr:lastModifiedBy property not present", content.hasProperty("jcr:lastModifiedBy"), |
| equalTo(true)); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void autoCreatedPropertiesAreUpdatedIfPresent() throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| Node content = session().getRootNode().addNode("content", "nt:folder"); |
| |
| session().save(); |
| |
| ResourceProxy resource = newResource("/content", "nt:folder"); |
| resource.addProperty("jcr:mixinTypes", "mix:lastModified"); |
| resource.addProperty("jcr:lastModifiedBy", "admin2"); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger); |
| cmd.execute().get(); |
| cmd.execute().get(); // second time since mixins are processed after properties so we need two |
| // executions to |
| // expose the problem |
| |
| session().refresh(false); |
| |
| content = session().getRootNode().getNode("content"); |
| assertThat("jcr:lastModifiedBy property not modified", content.getProperty("jcr:lastModifiedBy") |
| .getString(), equalTo("admin2")); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void setEmptyMixinTypes() throws Exception { |
| setMixinTypes0(); |
| } |
| |
| private void setMixinTypes0(final String... mixinTypeNames) throws Exception { |
| |
| doWithTransientRepository(new CallableWithSession() { |
| @Override |
| public Void call() throws Exception { |
| ResourceProxy resource = newResource("/content", "nt:unstructured"); |
| resource.addProperty("jcr:mixinTypes", mixinTypeNames); |
| |
| AddOrUpdateNodeCommand cmd = new AddOrUpdateNodeCommand(repo(), credentials(), DEFAULT_CONTEXT, null, resource, logger); |
| cmd.execute().get(); |
| |
| session().refresh(false); |
| |
| Node content = session().getRootNode().getNode("content"); |
| assertThat(content.getMixinNodeTypes(), Matchers.arrayWithSize(mixinTypeNames.length)); |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Test |
| public void setMixinTypes() throws Exception { |
| |
| setMixinTypes0("mix:created"); |
| } |
| |
| private void doWithTransientRepository(CallableWithSession callable) throws Exception { |
| |
| File out = new File(new File("target"), "jackrabbit"); |
| TransientRepository repo = new TransientRepository(new File(out, "repository.xml"), new File(out, "repository")); |
| SimpleCredentials credentials = new SimpleCredentials("admin", "admin".toCharArray()); |
| Session session = repo.login(credentials); |
| |
| importNodeTypeDefinitions(session, "test-definitions.cnd"); |
| importNodeTypeDefinitions(session, "folder.cnd"); |
| |
| try { |
| callable.setCredentials(credentials); |
| callable.setSession(session); |
| callable.call(); |
| } finally { |
| if (session.itemExists("/content")) |
| session.removeItem("/content"); |
| session.save(); |
| session.logout(); |
| } |
| |
| } |
| |
| private void importNodeTypeDefinitions(Session session, String cndFile) throws InvalidNodeTypeDefinitionException, |
| NodeTypeExistsException, UnsupportedRepositoryOperationException, ParseException, RepositoryException, |
| IOException { |
| try ( InputStream cndInput = getClass().getResourceAsStream(cndFile) ) { |
| if (cndInput == null) { |
| throw new IllegalArgumentException("Unable to read classpath resource " + cndFile); |
| } |
| CndImporter.registerNodeTypes(new InputStreamReader(cndInput), session); |
| } |
| } |
| |
| private static abstract class CallableWithSession implements Callable<Void> { |
| |
| private Session session; |
| private Credentials credentials; |
| |
| public void setSession(Session session) { |
| |
| this.session = session; |
| } |
| |
| public void setCredentials(Credentials credentials) { |
| |
| this.credentials = credentials; |
| } |
| |
| protected Session session() { |
| |
| if (session == null) |
| throw new IllegalStateException("session is null"); |
| |
| return session; |
| } |
| |
| protected Credentials credentials() { |
| |
| if (credentials == null) |
| throw new IllegalStateException("credentials is null"); |
| |
| return credentials; |
| } |
| |
| protected Repository repo() { |
| |
| return session().getRepository(); |
| } |
| } |
| } |