/**
* 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.easymock.EasyMock.*;
import static org.junit.Assert.*;
import info.magnolia.cms.beans.config.ContentRepository;
import info.magnolia.cms.beans.runtime.FileProperties;
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.importexport.PropertiesImportExport;
import info.magnolia.jcr.util.NodeTypes;
import info.magnolia.repository.Provider;
import info.magnolia.repository.RepositoryConstants;
import info.magnolia.test.RepositoryTestCase;
import info.magnolia.test.mock.jcr.MockNode;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.easymock.IAnswer;
import org.junit.Test;
/**
* @version $Id$
*/
public class DefaultContentTest extends RepositoryTestCase {
public interface ExceptionThrowingCallback {
void call() throws Exception;
}
@Test
public void testAddMixin() throws IOException, RepositoryException{
final Content content = getTestContent();
final String repoName = content.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.getJCRNode().canAddMixin(mixDeleted));
content.addMixin(mixDeleted);
}
@Test
public void testReadingANodeData() throws IOException, RepositoryException{
Content content = getTestContent();
NodeData nodeData = content.getNodeData("nd1");
assertEquals("hello", nodeData.getString());
assertEquals(true, nodeData.isExist());
}
@Test
public void testThatReadingANonExistingNodeDataDoesNotFail() throws IOException, RepositoryException{
Content content = getTestContent();
// this should not fail
NodeData nodeData = content.getNodeData("nd2");
assertEquals(false, nodeData.isExist());
}
@Test
public void testSettingAnExistingNodeData() throws IOException, RepositoryException{
Content content = getTestContent();
// this should not fail
Value value = createValue("test");
NodeData nodeData = content.setNodeData("nd1", value);
assertEquals("test", nodeData.getString());
}
@Test
public void testSettingANonExistingNodeDataCreatesANewNodeData() throws IOException, RepositoryException{
Content content = getTestContent();
Value value = createValue("test");
// does not exist yet
NodeData nodeData = content.setNodeData("nd2", value);
assertEquals("test", nodeData.getString());
}
@Test
public void testCreatingAnEmptyNodeData() throws IOException, RepositoryException{
Content content = getTestContent();
// this should not fail
NodeData nodeData = content.createNodeData("nd2");
assertEquals("", nodeData.getString());
assertEquals(true, nodeData.isExist());
}
@Test
public void testCreatingAnEmptyNodeDataSetsADefaultValueIfPossible() throws IOException, RepositoryException {
Content content = getTestContent();
NodeData nodeData = content.createNodeData("nd2", PropertyType.BOOLEAN);
assertEquals(true, nodeData.isExist());
assertEquals(PropertyType.BOOLEAN, nodeData.getType());
}
@Test
public void testCreatingAndSettingANodeData() throws IOException, RepositoryException{
Content content = getTestContent();
// this should not fail
NodeData nodeData = content.createNodeData("nd2", "test");
assertEquals("test", nodeData.getString());
}
@Test
public void testCreatingAndSettingABooleanNodeData() throws IOException, RepositoryException{
Content content = getTestContent();
// this actually creates a string property having an empty string value
NodeData nodeData = content.createNodeData("nd2");
// now setting a boolean value
nodeData.setValue(true);
assertEquals(true, nodeData.getBoolean());
nodeData = content.createNodeData("nd3", true);
assertEquals(true, nodeData.getBoolean());
}
@Test
public void testCreatingAnExistingNodeDataDoesNotFail() throws IOException, RepositoryException{
Content content = getTestContent();
NodeData nodeData = content.createNodeData("nd1", "other");
assertEquals("other", nodeData.getString());
}
@Test
public void testCreatingAndReadingABinaryNodeData() throws IOException, RepositoryException{
Content content = getTestContent();
String binaryContent = "the content";
NodeData nodeData = content.createNodeData("nd2", PropertyType.BINARY);
nodeData.setValue(IOUtils.toInputStream(binaryContent));
// nodeData.setAttribute(FileProperties.PROPERTY_FILENAME, "filename");
nodeData.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, "text/plain");
nodeData.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, Calendar.getInstance());
content.save();
nodeData = content.getNodeData("nd2");
assertEquals(binaryContent, IOUtils.toString(nodeData.getStream()));
//assertEquals("filename", nodeData.getAttribute(FileProperties.PROPERTY_FILENAME));
}
@Test
public void testThatReadingANonExistingNodeDataReturnsAnEmptyNodeData() throws IOException, RepositoryException{
Content content = getTestContent();
NodeData nd = content.getNodeData("nirvana");
assertEquals("nirvana", nd.getName());
assertEquals("", nd.getString());
assertEquals(false, nd.getBoolean());
assertEquals(0, nd.getLong());
assertEquals("", nd.getAttribute("other"));
}
@Test
public void testThatReadingANonExistingNodeDataReturnsAnEmptyNodeDataWhichIsUnmutable() throws IOException, RepositoryException{
Content content = getTestContent();
NodeData nd = content.getNodeData("nirvana");
try{
nd.setValue("value");
}
catch(ItemNotFoundException e){
return;
}
fail("should throw an exception");
}
private Content getTestContent() throws IOException, RepositoryException {
String contentProperties =
"/mycontent.@type=mgnl:content\n" +
"/mycontent.nd1=hello";
HierarchyManager hm = MgnlContext.getHierarchyManager(RepositoryConstants.WEBSITE);
new PropertiesImportExport().createContent(hm.getRoot(), IOUtils.toInputStream(contentProperties));
hm.save();
Content content = hm.getContent("/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(NodeTypes.Versionable.NAME);
node.getSession().save();
final Node version = VersionManager.getInstance().addVersion(node, new Rule(NodeTypes.ContentNode.NAME, ",")).getFrozenNode();
final DefaultContent content = new DefaultContent();
// WHEN-THEN
assertTrue(content.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(NodeTypes.Versionable.NAME);
node.getSession().save();
final Node version = VersionManager.getInstance().addVersion(node, new Rule(NodeTypes.ContentNode.NAME, ",")).getFrozenNode();
final DefaultContent content = new DefaultContent();
// WHEN-THEN
assertTrue(content.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(NodeTypes.Versionable.NAME);
node.getSession().save();
final Node version = VersionManager.getInstance().addVersion(node, new Rule(NodeTypes.ContentNode.NAME, ",")).getFrozenNode();
final DefaultContent content = new DefaultContent();
// WHEN-THEN
assertFalse(content.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(ItemType.JCR_PRIMARY_TYPE)).andReturn(nodeTypeProp);
expect(nodeTypeProp.getString()).andReturn(ItemType.NT_FROZENNODE);
expect(node.isNodeType(ItemType.NT_FROZENNODE)).andReturn(true);
replay(node, nodeTypeProp);
final DefaultContent c = new DefaultContent();
c.setNode(node);
assertTrue(c.isNodeType(node, ItemType.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=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=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=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=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 HierarchyManager hm = MgnlContext.getHierarchyManager(RepositoryConstants.WEBSITE);
new PropertiesImportExport().createContent(hm.getRoot(), IOUtils.toInputStream(contentProperties));
hm.save();
final Content content = hm.getContent("/somepage/mypage/paragraphs/0");
final Collection<NodeData> props = content.getNodeDataCollection("att*|ima*");
assertEquals(5, props.size());
// sort by name
final TreeSet<NodeData> sorted = new TreeSet<NodeData>(new Comparator<NodeData>() {
@Override
public int compare(NodeData o1, NodeData o2) {
return o1.getName().compareTo(o2.getName());
}
});
sorted.addAll(props);
// sanity check - just recheck we still have 5 elements
assertEquals(5, sorted.size());
final Iterator<NodeData> it = sorted.iterator();
final NodeData a = it.next();
final NodeData b = it.next();
final NodeData c = it.next();
final NodeData d = it.next();
final NodeData 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 = StringUtils.join(Arrays.asList(
"/hello/foo=bar",
// a binary
"/hello/bin@type=mgnl:resource",
"/hello/bin.fileName=hello",
"/hello/bin.extension=gif",
"/hello/bin=binary:some-data",
"/hello/bin.jcr\\:mimeType=image/gif",
"/hello/bin.jcr\\:lastModified=date:2009-10-14T08:59:01.227-04:00"), "\n");
final HierarchyManager hm = MgnlContext.getHierarchyManager(RepositoryConstants.WEBSITE);
new PropertiesImportExport().createContent(hm.getRoot(), IOUtils.toInputStream(contentProperties));
hm.save();
final Content content = hm.getContent("/hello");
final NodeData st = content.getNodeData("foo");
assertEquals(PropertyType.STRING, st.getType());
assertEquals("bar", st.getString());
assertEquals("bar", IOUtils.toString(st.getStream()));
final NodeData bin = content.getNodeData("bin");
assertEquals(PropertyType.BINARY, bin.getType());
assertEquals("some-data", IOUtils.toString(bin.getStream()));
assertEquals("some-data", bin.getString());
}
@Test
public void testModDate() throws IOException, RepositoryException{
Content content = getTestContent();
Calendar modDate = content.getMetaData().getModificationDate();
Calendar creationDate = content.getMetaData().getCreationDate();
assertNotNull(modDate);
assertEquals(creationDate, modDate);
content.setNodeData("test", false);
content.save();
modDate = content.getMetaData().getModificationDate();
assertNotNull(modDate);
assertNotSame(creationDate, modDate);
}
@Test
public void testDelete() throws Exception {
// GIVEN content node with version history
final Content content = getTestContent();
final String uuid = content.getUUID();
final Content parent = content.getParent();
content.addVersion();
// parent.save();
Content versionedContent = MgnlContext.getHierarchyManager("mgnlVersion").getContentByUUID(uuid);
assertNotNull(versionedContent);
VersionHistory history = versionedContent.getJCRNode().getVersionHistory();
// root and current
VersionIterator versions = content.getAllVersions();
// root version
assertNotNull(versions.nextVersion());
// previously created version
assertNotNull(versions.nextVersion());
// WHEN we delete the content
content.delete();
parent.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.
}
}
@Test
public void testEquals() {
// GIVEN
Node node = new MockNode("test");
DefaultContent first = new DefaultContent(node);
DefaultContent second = new DefaultContent(node);
// WHEN
boolean result = first.equals(second);
// THEN
assertTrue(result);
}
@Test
public void testEqualsWithNull() {
// GIVEN
Node node = new MockNode("test");
DefaultContent first = new DefaultContent(node);
// WHEN
boolean result = first.equals(null);
// THEN
assertFalse(result);
}
@Test
public void testEqualsWithWrongType() {
// GIVEN
Node node = new MockNode("test");
DefaultContent first = new DefaultContent(node);
Object second = "second";
// WHEN
boolean result = first.equals(second);
// THEN
assertFalse(result);
}
private Value createValue(Object valueObj) throws RepositoryException, UnsupportedRepositoryOperationException {
ValueFactory valueFactory = MgnlContext.getHierarchyManager("website").getWorkspace().getSession().getValueFactory();
return NodeDataUtil.createValue(valueObj, valueFactory);
}
}