/** * This file Copyright (c) 2003-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.cms.core; import static org.junit.Assert.*; import static org.easymock.EasyMock.*; import info.magnolia.cms.beans.config.ContentRepository; import info.magnolia.cms.core.version.VersionManager; import info.magnolia.cms.util.NodeDataUtil; import info.magnolia.cms.util.Rule; import info.magnolia.context.MgnlContext; import info.magnolia.jcr.util.NodeTypes; import info.magnolia.jcr.util.NodeUtil; import info.magnolia.jcr.util.PropertiesImportExport; import info.magnolia.repository.Provider; import info.magnolia.repository.RepositoryConstants; import info.magnolia.test.RepositoryTestCase; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; import java.util.TreeSet; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.JcrConstants; import org.easymock.IAnswer; import org.junit.Test; /** * Tests. */ public class NodeTest extends RepositoryTestCase { public interface ExceptionThrowingCallback { void call() throws Exception; } @Test public void testAddMixin() throws IOException, RepositoryException{ final Node content = getTestNode(); final String repoName = content.getSession().getWorkspace().getName(); final String mixDeleted = "mgnl:deleted"; final Provider repoProvider = ContentRepository.getRepositoryProvider(repoName); final String mgnlMixDeleted = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<nodeTypes" + " xmlns:rep=\"internal\"" + " xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\"" + " xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\"" + " xmlns:mgnl=\"http://www.magnolia.info/jcr/mgnl\"" + " xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" + "<nodeType name=\"" + mixDeleted + "\" isMixin=\"true\" hasOrderableChildNodes=\"true\" primaryItemName=\"\">" + "<supertypes>" + "<supertype>nt:base</supertype>" + "</supertypes>" + "</nodeType>" + "</nodeTypes>"; try { repoProvider.registerNodeTypes(new ByteArrayInputStream(mgnlMixDeleted.getBytes())); } catch (RepositoryException e) { // ignore, either it's already registered and test will pass, or type can't be registered and test should fail } assertTrue(content.canAddMixin(mixDeleted)); content.addMixin(mixDeleted); } @Test public void testReadingANodeData() throws IOException, RepositoryException{ Node content = getTestNode(); Property nodeData = content.getProperty("nd1"); assertEquals("hello", nodeData.getString()); assertFalse(nodeData.isNew()); } @Test public void testThatReadingANonExistingNodeDataFail() throws IOException, RepositoryException { Node content = getTestNode(); try { // this should fail Property nodeData = content.getProperty("nd2"); fail("should have failed to retrieve non existing property"); } catch (PathNotFoundException e) { // ignore } } @Test public void testSettingAnExistingNodeData() throws IOException, RepositoryException{ Node content = getTestNode(); // this should not fail Value value = createValue("test"); Property nodeData = content.setProperty("nd1", value); assertEquals("test", nodeData.getString()); } @Test public void testSettingANonExistingNodeDataCreatesANewNodeData() throws IOException, RepositoryException{ Node content = getTestNode(); Value value = createValue("test"); // does not exist yet Property nodeData = content.setProperty("nd2", value); assertEquals("test", nodeData.getString()); } // TODO: review JCR Node doesn't support empty properties! // public void testCreatingAnEmptyNodeDataSetsADefaultValueIfPossible() throws IOException, RepositoryException { // Node content = getTestNode(); // Property nodeData = content.setProperty("nd2", PropertyType.BOOLEAN); // assertTrue(nodeData.isNew()); // assertEquals(PropertyType.BOOLEAN, nodeData.getType()); // } @Test public void testCreatingAndSettingANodeData() throws IOException, RepositoryException{ Node content = getTestNode(); // this should not fail Property nodeData = content.setProperty("nd2", "test"); assertEquals("test", nodeData.getString()); } @Test public void testCreatingAnExistingNodeDataDoesNotFail() throws IOException, RepositoryException{ Node content = getTestNode(); Property nodeData = content.setProperty("nd1", "other"); assertEquals("other", nodeData.getString()); } // TODO: review if binary node data handling // public void testCreatingAndReadingABinaryNodeData() throws IOException, RepositoryException{ // Node content = getTestNode(); // String binaryContent = "the content"; // Property nodeData = content.setProperty("nd2", PropertyType.BINARY); // nodeData.setValue(IOUtils.toInputStream(binaryContent)); // nodeData.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, "text/plain"); // nodeData.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, Calendar.getInstance()); // // content.save(); // Property = content.getProperty("nd2"); // assertEquals(binaryContent, IOUtils.toString(nodeData.getStream())); // //assertEquals("filename", nodeData.getAttribute(FileProperties.PROPERTY_FILENAME)); // } // TODO: review JCR Node doesn't support empty properties! // public void testThatReadingANonExistingNodeDataReturnsAnEmptyNodeData() throws IOException, RepositoryException{ // Node content = getTestNode(); // Property nd = content.getProperty("nirvana"); // assertEquals("nirvana", nd.getName()); // assertEquals("", nd.getString()); // assertEquals(false, nd.getBoolean()); // assertEquals(0, nd.getLong()); // } // TODO: review JCR Node doesn't support empty properties! // public void testThatReadingANonExistingNodeDataReturnsAnEmptyNodeDataWhichIsUnmutable() throws IOException, RepositoryException{ // Node content = getTestNode(); // Property nd = content.getProperty("nirvana"); // try{ // nd.setValue("value"); // } // catch(ItemNotFoundException e){ // return; // } // fail("should throw an exception"); // } private Node getTestNode() throws IOException, RepositoryException { String contentProperties = "/mycontent.@type=mgnl:content\n" + "/mycontent.nd1=hello"; Session hm = MgnlContext.getJCRSession(RepositoryConstants.WEBSITE); new PropertiesImportExport().createNodes(hm.getRootNode(), IOUtils.toInputStream(contentProperties)); hm.save(); Node content = hm.getNode("/mycontent"); return content; } // public void testPermissionCheckedOnDeleteNodeData() throws Exception { // HierarchyManager hm = MgnlContext.getHierarchyManager(RepositoryConstants.WEBSITE); // // create the content while we have full permissions // final Content node = hm.createContent("/", "foo", ItemType.CONTENTNODE.getSystemName()); // node.createNodeData("bar").setValue("test"); // // AccessManager am = new AccessManagerImpl(); // setPermission(am, "/*", Permission.READ); // // // test that we can read // assertTrue(node.hasNodeData("bar")); // assertEquals("test", node.getNodeData("bar").getString()); // // mustFailWithAccessDeniedException(new ExceptionThrowingCallback() { // public void call() throws Exception { // node.setNodeData("bar", "other"); // } // // }, "should not be allowed to set a value"); // // mustFailWithAccessDeniedException(new ExceptionThrowingCallback() { // public void call() throws Exception { // node.delete("bar"); // } // // }, "should not be allowed to delete a nodedata"); // // mustFailWithAccessDeniedException(new ExceptionThrowingCallback() { // public void call() throws Exception { // node.deleteNodeData("bar"); // } // // }, "should not be allowed to delete a nodedata"); // } // // private void mustFailWithAccessDeniedException(ExceptionThrowingCallback callback, String msg) throws Exception { // try{ // callback.call(); // } // catch (AccessDeniedException e) { // // this expected // return; // } // fail(msg); // // } // // private void setPermission(AccessManager am, String path, long permissionValue) { // List<Permission> permissions = am.getPermissionList(); // if(permissions == null){ // permissions = new ArrayList<Permission>(); // } // // PermissionImpl permission = new PermissionImpl(); // permission.setPattern(new SimpleUrlPattern(path)); // permission.setPermissions(permissionValue); // permissions.add(permission); // am.setPermissionList(permissions); // } @Test public void testIsNodeTypeForNodeChecksPrimaryType() throws RepositoryException { final Node node = createMock(Node.class); final Property nodeTypeProp = createStrictMock(Property.class); expect(node.getProperty(ItemType.JCR_PRIMARY_TYPE)).andReturn(nodeTypeProp).times(2); expect(node.isNodeType((String)anyObject())).andAnswer(new IAnswer<Boolean>(){ @Override public Boolean answer() throws Throwable { return getCurrentArguments()[0].equals("foo"); } }).times(2); expect(nodeTypeProp.getString()).andReturn("foo").times(2); replay(node, nodeTypeProp); final DefaultContent c = new DefaultContent(); c.setNode(node); assertTrue(c.isNodeType(node, "foo")); assertFalse(c.isNodeType(node, "bar")); verify(node, nodeTypeProp); } @Test public void testIsNodeTypeForNodeCheckFrozenTypeIfWereNotLookingForFrozenNodes() throws Exception { // GIVEN final Node node = MgnlContext.getJCRSession(RepositoryConstants.WEBSITE).getRootNode().addNode("testPage", NodeTypes.ContentNode.NAME); node.addMixin(JcrConstants.MIX_VERSIONABLE); node.getSession().save(); final Node version = VersionManager.getInstance().addVersion(node, new Rule(NodeTypes.ContentNode.NAME, ",")).getFrozenNode(); // WHEN-THEN assertTrue(NodeUtil.isNodeType(version, NodeTypes.ContentNode.NAME)); } @Test public void testIsNodeTypeForNodeCheckFrozenTypeForSupertypesIfWereNotLookingForFrozenNodes() throws Exception { // GIVEN final Node node = MgnlContext.getJCRSession(RepositoryConstants.WEBSITE).getRootNode().addNode("testPage", NodeTypes.Area.NAME); node.addMixin(JcrConstants.MIX_VERSIONABLE); node.getSession().save(); final Node version = VersionManager.getInstance().addVersion(node, new Rule(NodeTypes.ContentNode.NAME, ",")).getFrozenNode(); // WHEN-THEN assertTrue(NodeUtil.isNodeType(version, NodeTypes.ContentNode.NAME)); } @Test public void testIsNotNodeTypeForNodeCheckFrozenTypeIfWereNotLookingForFrozenNodes() throws Exception { // GIVEN final Node node = MgnlContext.getJCRSession(RepositoryConstants.WEBSITE).getRootNode().addNode("testPage", NodeTypes.Content.NAME); node.addMixin(JcrConstants.MIX_VERSIONABLE); node.getSession().save(); final Node version = VersionManager.getInstance().addVersion(node, new Rule(NodeTypes.ContentNode.NAME, ",")).getFrozenNode(); // WHEN-THEN assertFalse(NodeUtil.isNodeType(version, NodeTypes.ContentNode.NAME)); } @Test public void testIsNodeTypeForNodeDoesNotCheckFrozenTypeIfTheRequestedTypeIsFrozenType()throws RepositoryException { final Node node = createStrictMock(Node.class); final Property nodeTypeProp = createStrictMock(Property.class); expect(node.getProperty(JcrConstants.JCR_PRIMARYTYPE)).andReturn(nodeTypeProp); expect(nodeTypeProp.getString()).andReturn(JcrConstants.NT_FROZENNODE); expect(node.isNodeType(JcrConstants.NT_FROZENNODE)).andReturn(true); replay(node, nodeTypeProp); final DefaultContent c = new DefaultContent(); c.setNode(node); assertTrue(c.isNodeType(node, JcrConstants.NT_FROZENNODE)); verify(node, nodeTypeProp); } @Test public void testNameFilteringWorksForBothBinaryAndNonBinaryProperties() throws Exception { String contentProperties = StringUtils.join(Arrays.asList( "/somepage/mypage.@type=mgnl:content", "/somepage/mypage/paragraphs.@type=mgnl:contentNode", "/somepage/mypage/paragraphs/0.@type=mgnl:contentNode", "/somepage/mypage/paragraphs/0.@type=mgnl:contentNode", // 2 regular props "/somepage/mypage/paragraphs/0.attention=booyah", "/somepage/mypage/paragraphs/0.imaginary=date:2009-10-14T08:59:01.227-04:00", // 3 binaries "/somepage/mypage/paragraphs/0/attachment1.@type=mgnl:resource", "/somepage/mypage/paragraphs/0/attachment1.fileName=hello", "/somepage/mypage/paragraphs/0/attachment1.extension=gif", // being a binary node, magnolia knows to store data as jcr:data w/o need to be explicitly told so "/somepage/mypage/paragraphs/0/attachment1.jcr\\:data=binary:X", "/somepage/mypage/paragraphs/0/attachment1.jcr\\:mimeType=image/gif", "/somepage/mypage/paragraphs/0/attachment1.jcr\\:lastModified=date:2009-10-14T08:59:01.227-04:00", "/somepage/mypage/paragraphs/0/attachment2.@type=mgnl:resource", "/somepage/mypage/paragraphs/0/attachment2.fileName=test", "/somepage/mypage/paragraphs/0/attachment2.extension=jpeg", "/somepage/mypage/paragraphs/0/attachment2.jcr\\:data=binary:X", "/somepage/mypage/paragraphs/0/attachment2.jcr\\:mimeType=image/jpeg", "/somepage/mypage/paragraphs/0/attachment2.jcr\\:lastModified=date:2009-10-14T08:59:01.227-04:00", "/somepage/mypage/paragraphs/0/image3.@type=mgnl:resource", "/somepage/mypage/paragraphs/0/image3.fileName=third", "/somepage/mypage/paragraphs/0/image3.extension=png", "/somepage/mypage/paragraphs/0/image3.jcr\\:data=binary:X", "/somepage/mypage/paragraphs/0/image3.jcr\\:mimeType=image/png", "/somepage/mypage/paragraphs/0/image3.jcr\\:lastModified=date:2009-10-14T08:59:01.227-04:00", // and more which should not match "/somepage/mypage/paragraphs/0.foo=bar", "/somepage/mypage/paragraphs/0.mybool=boolean:true", "/somepage/mypage/paragraphs/0/rand.@type=mgnl:resource", "/somepage/mypage/paragraphs/0/rand.fileName=randdddd", "/somepage/mypage/paragraphs/0/rand.extension=png", "/somepage/mypage/paragraphs/0/rand.jcr\\:data=binary:X", "/somepage/mypage/paragraphs/0/rand.jcr\\:mimeType=image/png", "/somepage/mypage/paragraphs/0/rand.jcr\\:lastModified=date:2009-10-14T08:59:01.227-04:00" ), "\n"); final Session hm = MgnlContext.getJCRSession(RepositoryConstants.WEBSITE); new PropertiesImportExport().createNodes(hm.getRootNode(), IOUtils.toInputStream(contentProperties)); hm.save(); final Node content = hm.getNode("/somepage/mypage/paragraphs/0"); final PropertyIterator props = content.getProperties("att*|ima*"); // sort by name final TreeSet<Property> sorted = new TreeSet<Property>(new Comparator<Property>() { @Override public int compare(Property o1, Property o2) { try { return o1.getName().compareTo(o2.getName()); } catch (RepositoryException e) { throw new RuntimeException(e); } } }); while (props.hasNext()) { sorted.add(props.nextProperty()); } // TODO: SCRUM-306 (3 binary props are now nodes not props) // assertEquals(5, sorted.size()); // // final Iterator<Property> it = sorted.iterator(); // final Property a = it.next(); // final Property b = it.next(); // final Property c = it.next(); // final Property d = it.next(); // final Property e = it.next(); // assertEquals("attachment1", a.getName()); // assertEquals(PropertyType.BINARY, a.getType()); // assertEquals("attachment2", b.getName()); // assertEquals(PropertyType.BINARY, b.getType()); // assertEquals("image3", d.getName()); // assertEquals(PropertyType.BINARY, d.getType()); // assertEquals("image3", d.getName()); // assertEquals(PropertyType.BINARY, d.getType()); // // assertEquals("attention", c.getName()); // assertEquals(PropertyType.STRING, c.getType()); // assertEquals("booyah", c.getString()); // assertEquals("imaginary", e.getName()); // assertEquals(PropertyType.DATE, e.getType()); // assertEquals(true, e.getDate().before(Calendar.getInstance())); } @Test public void testStringPropertiesCanBeRetrievedByStreamAndViceVersa() throws Exception { String[] contentProperties = new String[] { "/hello.foo=bar", // a binary "/hello/bin.@type=mgnl:resource", "/hello/bin.fileName=hello", "/hello/bin.extension=gif", "/hello/bin.jcr\\:data=binary:some-data", "/hello/bin.jcr\\:mimeType=image/gif", "/hello/bin.jcr\\:lastModified=date:2009-10-14T08:59:01.227-04:00" }; final Session hm = MgnlContext.getJCRSession(RepositoryConstants.WEBSITE); new PropertiesImportExport().createNodes(hm.getRootNode(), contentProperties); hm.save(); final Node content = hm.getNode("/hello"); final Property st = content.getProperty("foo"); assertEquals(PropertyType.STRING, st.getType()); assertEquals("bar", st.getString()); assertEquals("bar", IOUtils.toString(st.getStream())); // TODO: SCRUM-306 // final Property bin = content.getProperty("bin"); // assertEquals(PropertyType.BINARY, bin.getType()); // assertEquals("some-data", IOUtils.toString(bin.getStream())); // assertEquals("some-data", bin.getString()); } private Value createValue(Object valueObj) throws RepositoryException, UnsupportedRepositoryOperationException { ValueFactory valueFactory = MgnlContext.getHierarchyManager("website").getWorkspace().getSession().getValueFactory(); return NodeDataUtil.createValue(valueObj, valueFactory); } // TODO: SCRUM-308 // public void testModDate() throws IOException, RepositoryException{ // Node content = getTestNode(); // Calendar modDate = MetaDataUtil.getMetaData(content).getModificationDate(); // Calendar creationDate = MetaDataUtil.getMetaData(content).getCreationDate(); // assertNotNull(modDate); // assertEquals(creationDate, modDate); // content.setProperty("test", false); // content.save(); // modDate = MetaDataUtil.getMetaData(content).getModificationDate(); // assertNotNull(modDate); // assertNotSame(creationDate, modDate); // } // TODO: SCRUM-310 // public void testDelete() throws Exception { // // GIVEN content node with version history // final Node content = getTestNode(); // final String uuid = content.getIdentifier(); // final Node parent = content.getParent(); // VersionManager.getInstance().addVersion(content); // parent.save(); // Node versionedContent = MgnlContext.getJCRSession("mgnlVersion").getNodeByIdentifier(uuid); // assertNotNull(versionedContent); // VersionHistory history = versionedContent.getVersionHistory(); // // root and current // VersionIterator versions = content.getVersionHistory().getAllVersions(); // // root version // assertNotNull(versions.nextVersion()); // // previously created version // assertNotNull(versions.nextVersion()); // // // WHEN we delete the content // content.remove(); // parent.getSession().save(); // // // THEN versioned node and all versions should be deleted as well // // make sure versioned node is deleted // try { // MgnlContext.getHierarchyManager("mgnlVersion").getContentByUUID(uuid); // fail("versioned copy should have been deleted but was not."); // } catch (ItemNotFoundException e) { // // expected // } // // // make sure history has no label => all versions incl. root are gone // try { // history.getVersionLabels(); // fail("version history should have been invalidated by JR after manually deleting all versions except root and referencing content"); // } catch (RepositoryException e) { // // no versions exist anymore. // } // } }