/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package org.alfresco.repo.blog;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.blog.BlogPostInfo;
import org.alfresco.service.cmr.blog.BlogService;
import org.alfresco.service.cmr.blog.BlogService.RangedDateProperty;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.service.cmr.tagging.TaggingService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyMap;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
/**
* Test cases for {@link BlogServiceImpl}.
*
* @author Neil Mc Erlean
* @since 4.0
*/
public class BlogServiceImplTest
{
private static final ApplicationContext testContext = ApplicationContextHelper.getApplicationContext();
// injected services
private static MutableAuthenticationService AUTHENTICATION_SERVICE;
private static BehaviourFilter BEHAVIOUR_FILTER;
private static BlogService BLOG_SERVICE;
private static DictionaryService DICTIONARY_SERVICE;
private static NodeService NODE_SERVICE;
private static PersonService PERSON_SERVICE;
private static RetryingTransactionHelper TRANSACTION_HELPER;
private static SiteService SITE_SERVICE;
private static TaggingService TAGGING_SERVICE;
private static final String TEST_USER = BlogServiceImplTest.class.getSimpleName() + "_testuser";
private static final String ADMIN_USER = AuthenticationUtil.getAdminUserName();
/**
* Temporary test nodes (created during a test method) that need deletion after the test method.
*/
private List testNodesToTidy = new ArrayList();
/**
* Temporary test nodes (created BeforeClass) that need deletion after this test class.
*/
private static List CLASS_TEST_NODES_TO_TIDY = new ArrayList();
private static SiteInfo BLOG_SITE;
private static NodeRef BLOG_CONTAINER_NODE;
@BeforeClass public static void initTestsContext() throws Exception
{
AUTHENTICATION_SERVICE = (MutableAuthenticationService)testContext.getBean("authenticationService");
BEHAVIOUR_FILTER = (BehaviourFilter)testContext.getBean("policyBehaviourFilter");
BLOG_SERVICE = (BlogService)testContext.getBean("blogService");
DICTIONARY_SERVICE = (DictionaryService)testContext.getBean("dictionaryService");
NODE_SERVICE = (NodeService)testContext.getBean("nodeService");
PERSON_SERVICE = (PersonService)testContext.getBean("personService");
TRANSACTION_HELPER = (RetryingTransactionHelper)testContext.getBean("retryingTransactionHelper");
SITE_SERVICE = (SiteService)testContext.getBean("siteService");
TAGGING_SERVICE = (TaggingService)testContext.getBean("TaggingService");
AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
createUser(TEST_USER);
// We need to create the test site as the test user so that they can contribute content to it in tests below.
AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER);
createTestSiteWithBlogContainer();
}
private static void createTestSiteWithBlogContainer() throws Exception
{
BLOG_SITE = TRANSACTION_HELPER.doInTransaction(
new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public SiteInfo execute() throws Throwable
{
SiteInfo site = SITE_SERVICE.createSite("BlogSitePreset", BlogServiceImplTest.class.getSimpleName() + "_testSite" + GUID.generate(),
"test site title", "test site description", SiteVisibility.PUBLIC);
CLASS_TEST_NODES_TO_TIDY.add(site.getNodeRef());
return site;
}
});
BLOG_CONTAINER_NODE = TRANSACTION_HELPER.doInTransaction(
new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public NodeRef execute() throws Throwable
{
SiteInfo site = BLOG_SITE;
NodeRef result = SITE_SERVICE.getContainer(site.getShortName(), BlogServiceImpl.BLOG_COMPONENT);
if (result == null)
{
result = SITE_SERVICE.createContainer(site.getShortName(), BlogServiceImpl.BLOG_COMPONENT,
ContentModel.TYPE_FOLDER, null);
CLASS_TEST_NODES_TO_TIDY.add(result);
}
return result;
}
});
}
/**
* By default, all tests are run as the admin user.
*/
@Before public void setAdminUser()
{
AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
}
@After public void deleteTestNodes() throws Exception
{
performDeletionOfNodes(testNodesToTidy);
}
@AfterClass public static void deleteClassTestNodesAndUsers() throws Exception
{
performDeletionOfNodes(CLASS_TEST_NODES_TO_TIDY);
deleteUser(TEST_USER);
}
/**
* Deletes the specified NodeRefs, if they exist.
* @param nodesToDelete
*/
private static void performDeletionOfNodes(final List nodesToDelete)
{
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public Void execute() throws Throwable
{
AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
for (NodeRef node : nodesToDelete)
{
if (NODE_SERVICE.exists(node))
{
// st:site nodes can only be deleted via the SiteService
if (NODE_SERVICE.getType(node).equals(SiteModel.TYPE_SITE))
{
SiteInfo siteInfo = SITE_SERVICE.getSite(node);
SITE_SERVICE.deleteSite(siteInfo.getShortName());
}
else
{
NODE_SERVICE.deleteNode(node);
}
}
}
return null;
}
});
}
@Test public void createDraftBlogPostsAndGetPagedResults() throws Exception
{
final int arbitraryNumberGreaterThanPageSize = 42;
final List submittedBlogPosts = TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback>()
{
@Override
public List execute() throws Throwable
{
List results = new ArrayList();
for (int i = 0; i < arbitraryNumberGreaterThanPageSize; i++)
{
BlogPostInfo newBlogPost;
if(i % 2 == 0)
{
// By container ref
newBlogPost = BLOG_SERVICE.createBlogPost(BLOG_CONTAINER_NODE, "title_" + i, "Hello world", true);
}
else
{
// By site name
newBlogPost = BLOG_SERVICE.createBlogPost(BLOG_SITE.getShortName(), "title_" + i, "Hello world", true);
}
results.add(newBlogPost.getNodeRef());
testNodesToTidy.add(newBlogPost.getNodeRef());
}
return results;
}
});
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public Void execute() throws Throwable
{
List recoveredBlogPosts = new ArrayList(arbitraryNumberGreaterThanPageSize);
final int pageSize = 10;
PagingRequest pagingReq = new PagingRequest(0, pageSize, null);
PagingResults pagedResults = BLOG_SERVICE.getDrafts(BLOG_CONTAINER_NODE, ADMIN_USER, pagingReq);
assertEquals("Wrong total result count.", arbitraryNumberGreaterThanPageSize, (int)pagedResults.getTotalResultCount().getFirst());
while (pagedResults.hasMoreItems())
{
recoveredBlogPosts.addAll(pagedResults.getPage());
pagingReq = new PagingRequest(pagingReq.getSkipCount() + pageSize, pageSize, null);
pagedResults = BLOG_SERVICE.getDrafts(BLOG_CONTAINER_NODE, ADMIN_USER, pagingReq);
}
// and the last page, which only has 2 items in it.
recoveredBlogPosts.addAll(pagedResults.getPage());
assertEquals("Wrong number of blog posts.", submittedBlogPosts.size(), recoveredBlogPosts.size());
// Check the list is sorted by cm:created, descending order.
assertNodeRefsAreSortedBy(recoveredBlogPosts, ContentModel.PROP_CREATED, false);
return null;
}
});
}
/**
* This method asserts that the given List has NodeRefs in order of the specified date property.
*
* @param blogPosts
* @param property a Date property
* @param ascendingOrder true if ascending order, false for descending.
*/
private void assertNodeRefsAreSortedBy(List blogPosts, QName property, boolean ascendingOrder)
{
final PropertyDefinition propertyDef = DICTIONARY_SERVICE.getProperty(property);
assertNotNull("Property not recognised.", propertyDef);
assertEquals("Property was not a Date", DataTypeDefinition.DATETIME, propertyDef.getDataType().getName());
if (blogPosts.size() > 1)
{
for (int i = 0; i < blogPosts.size() - 1; i++)
{
NodeRef nodeRef1 = blogPosts.get(i).getNodeRef();
NodeRef nodeRef2 = blogPosts.get(i + 1).getNodeRef();
Date date1 = (Date) NODE_SERVICE.getProperty(nodeRef1, property);
Date date2 = (Date) NODE_SERVICE.getProperty(nodeRef2, property);
// Equal dates are applicable to either sort order
if (date1.equals(date2))
{
continue;
}
if (ascendingOrder)
{
assertTrue("BlogPosts not asc-sorted by " + property + ". Error at index " + i, date1.before(date2));
}
else
{
assertTrue("BlogPosts not desc-sorted by " + property + ". Error at index " + i, date1.after(date2));
}
}
}
}
@Test public void createTaggedDraftBlogPost() throws Exception
{
// Our tags, which are a mixture of English, Accented European and Chinese
final List tags = Arrays.asList(new String[]{
"alpha", "beta", "gamma", "fran\u00e7ais", "chinese_\u535a\u5ba2"});
// Create a list of Blog Posts, all drafts, each with one of the tags above.
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback>()
{
@Override
public List execute() throws Throwable
{
List results = new ArrayList();
for (String tag : tags)
{
final String blogTitle = "draftWithTag" + tag;
BlogPostInfo newBlogPost = BLOG_SERVICE.createBlogPost(BLOG_CONTAINER_NODE, blogTitle, "Hello world", true);
TAGGING_SERVICE.addTags(newBlogPost.getNodeRef(), Arrays.asList(new String[]{tag}));
testNodesToTidy.add(newBlogPost.getNodeRef());
results.add(newBlogPost.getNodeRef());
}
return results;
}
});
// Check we get the correct tags back
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public Void execute() throws Throwable
{
// Now we'll recover these blogposts & we should expect to find the same tags.
Set expectedTags = new HashSet();
expectedTags.addAll(tags);
PagingRequest pagingReq = new PagingRequest(0, 10, null);
PagingResults pagedResults = BLOG_SERVICE.getDrafts(BLOG_CONTAINER_NODE, ADMIN_USER, pagingReq);
assertEquals("Wrong number of blog posts", tags.size(), pagedResults.getPage().size());
for (BlogPostInfo bpi : pagedResults.getPage())
{
NodeRef blogNode = bpi.getNodeRef();
List recoveredTags = TAGGING_SERVICE.getTags(blogNode);
assertEquals("Wrong number of tags", 1, recoveredTags.size());
String tag = recoveredTags.get(0);
assertTrue("Tag found on node but not expected: " + tag, expectedTags.remove(tag));
}
assertTrue("Not all tags were recovered from a blogpost", expectedTags.isEmpty());
return null;
}
});
// Check we can find the posts by their tags
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public Void execute() throws Throwable
{
PagingRequest pagingReq = new PagingRequest(0, 10, null);
RangedDateProperty dates = new RangedDateProperty(null, null, ContentModel.PROP_CREATED);
for (String tag : tags)
{
PagingResults pagedResults =
BLOG_SERVICE.findBlogPosts(BLOG_CONTAINER_NODE, dates, tag, pagingReq);
// Check we found our post
assertEquals("Wrong number of blog posts for " + tag, 1, pagedResults.getPage().size());
}
return null;
}
});
}
/**
* This test method uses the eventually consistent find*() method and so may fail if Lucene is disabled.
*/
@Test public void findBlogPostsByPublishedDate() throws Exception
{
final List tags = Arrays.asList(new String[]{"hello", "goodbye"});
// Going to set some specific published dates on these blog posts & query by date.
final Calendar cal = Calendar.getInstance();
cal.set(1971, 6, 15);
final Date _1971 = cal.getTime();
cal.set(1975, 0, 1);
final Date _1975 = cal.getTime();
cal.set(1980, 0, 1);
final Date _1980 = cal.getTime();
cal.set(1981, 0, 1);
final Date _1981 = cal.getTime();
cal.set(1985, 6, 15);
final Date _1985 = cal.getTime();
cal.set(1991, 6, 15);
final Date _1991 = cal.getTime();
final Map blogPosts = TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback