ALF-9385. Refactoring of the findTaggedBlogPosts method.

Now a more general  findBlogPosts(), which simplifies the webscript implementation slightly & removes possibility of an UnsupportedOperationException.
  Also added new test case that finds before/after/within specified date ranges.



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28988 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Neil McErlean
2011-07-13 15:33:32 +00:00
parent 128d5c9afc
commit f58f9c91c4
4 changed files with 275 additions and 6 deletions

View File

@@ -63,6 +63,8 @@
<bean id="blogService" class="org.alfresco.repo.blog.BlogServiceImpl">
<property name="cannedQueryRegistry" ref="cannedQueryRegistry"/>
<property name="contentService" ref="ContentService"/>
<property name="dictionaryService" ref="DictionaryService"/>
<property name="namespaceService" ref="NamespaceService"/>
<property name="nodeService" ref="NodeService"/>
<property name="permissionService" ref="PermissionService"/>
<property name="searchService" ref="SearchService"/>

View File

@@ -35,8 +35,12 @@ import org.alfresco.repo.blog.cannedqueries.DraftsAndPublishedBlogPostsCannedQue
import org.alfresco.repo.blog.cannedqueries.GetBlogPostsCannedQuery;
import org.alfresco.repo.blog.cannedqueries.GetBlogPostsCannedQueryFactory;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.search.impl.lucene.LuceneUtils;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.blog.BlogService;
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.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
@@ -75,6 +79,8 @@ public class BlogServiceImpl implements BlogService
private DraftsAndPublishedBlogPostsCannedQueryFactory draftsAndPublishedBlogPostsCannedQueryFactory;
private ContentService contentService;
private DictionaryService dictionaryService;
private NamespaceService namespaceService;
private NodeService nodeService;
private PermissionService permissionService;
private SearchService searchService;
@@ -109,6 +115,16 @@ public class BlogServiceImpl implements BlogService
this.contentService = contentService;
}
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
@@ -266,13 +282,19 @@ public class BlogServiceImpl implements BlogService
}
@Override
public PagingResults<BlogPostInfo> findTaggedBlogPosts(
NodeRef blogContainerNode, String tag, PagingRequest pagingReq)
public PagingResults<BlogPostInfo> findBlogPosts(NodeRef blogContainerNode, RangedDateProperty dateRange, String tag, PagingRequest pagingReq)
{
StringBuilder luceneQuery = new StringBuilder();
luceneQuery.append("+TYPE:\"").append(ContentModel.TYPE_CONTENT).append("\" ")
.append("+PARENT:\"").append(blogContainerNode.toString()).append("\" ")
.append("+PATH:\"/cm:taggable/cm:").append(ISO9075.encode(tag)).append("/member\"");
.append("+PARENT:\"").append(blogContainerNode.toString()).append("\" ");
if (tag != null && !tag.trim().isEmpty())
{
luceneQuery.append("+PATH:\"/cm:taggable/cm:").append(ISO9075.encode(tag)).append("/member\"");
}
if (dateRange != null)
{
luceneQuery.append(createDateRangeQuery(dateRange.getFromDate(), dateRange.getToDate(), dateRange.getDateProperty()));
}
SearchParameters sp = new SearchParameters();
sp.addStore(blogContainerNode.getStoreRef());
@@ -330,4 +352,73 @@ public class BlogServiceImpl implements BlogService
return results;
}
/**
* This method creates a Lucene query fragment which constrains the specified dateProperty to a range
* given by the fromDate and toDate parameters.
*
* @param fromDate the start of the date range (defaults to 1970-01-01 00:00:00 if null).
* @param toDate the end of the date range (defaults to 3000-12-31 00:00:00 if null).
* @param dateProperty the Alfresco property value to check against the range (must be a valid Date or DateTime property).
*
* @return the Lucene query fragment.
*
* @throws NullPointerException if dateProperty is null or if the dateProperty is not recognised by the system.
* @throws IllegalArgumentException if dateProperty refers to a property that is not of type {@link DataTypeDefinition#DATE} or {@link DataTypeDefinition#DATETIME}.
*/
private String createDateRangeQuery(Date fromDate, Date toDate, QName dateProperty)
{
// Some sanity checking of the date property.
if (dateProperty == null)
{
throw new NullPointerException("dateProperty cannot be null");
}
PropertyDefinition propDef = dictionaryService.getProperty(dateProperty);
if (propDef == null)
{
throw new NullPointerException("dateProperty '" + dateProperty + "' not recognised.");
}
else
{
final QName propDefType = propDef.getDataType().getName();
if ( !DataTypeDefinition.DATE.equals(propDefType) &&
!DataTypeDefinition.DATETIME.equals(propDefType))
{
throw new IllegalArgumentException("Illegal property type '" + dateProperty + "' [" + propDefType + "]");
}
}
QName propertyName = propDef.getName();
final String shortFormQName = propertyName.toPrefixString(namespaceService);
final String prefix = shortFormQName.substring(0, shortFormQName.indexOf(QName.NAMESPACE_PREFIX));
final String localName = propertyName.getLocalName();
// I can see potential issues with using 1970 and 3000 as default dates, but this is what the previous
// JavaScript controllers/libs did and I'll reproduce it here.
final String ZERO_DATE = "1970\\-01\\-01T00:00:00";
final String FUTURE_DATE = "3000\\-12\\-31T00:00:00";
StringBuilder luceneQuery = new StringBuilder();
luceneQuery.append(" +@").append(prefix).append("\\:").append(localName).append(":[");
if (fromDate != null)
{
luceneQuery.append(LuceneUtils.getLuceneDateString(fromDate));
}
else
{
luceneQuery.append(ZERO_DATE);
}
luceneQuery.append(" TO ");
if (toDate != null)
{
luceneQuery.append(LuceneUtils.getLuceneDateString(toDate));
}
else
{
luceneQuery.append(FUTURE_DATE);
}
luceneQuery.append("] ");
return luceneQuery.toString();
}
}

View File

@@ -22,11 +22,16 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.Serializable;
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;
@@ -37,9 +42,11 @@ import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.blog.BlogService;
import org.alfresco.service.cmr.blog.BlogService.BlogPostInfo;
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.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
@@ -330,6 +337,131 @@ public class BlogServiceImplTest
});
}
/**
* 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<String> 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<Integer, NodeRef> blogPosts = TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Map<Integer, NodeRef>>()
{
@Override
public Map<Integer, NodeRef> execute() throws Throwable
{
Map<Integer, NodeRef> result = new HashMap<Integer, NodeRef>();
// Create some blog posts. They'll all be published 'now' of course...
final BlogPostInfo blogPost1971 = BLOG_SERVICE.createBlogPost(BLOG_CONTAINER_NODE, "publishedPostWithTags1971", "Hello world", true);
final BlogPostInfo blogPost1981 = BLOG_SERVICE.createBlogPost(BLOG_CONTAINER_NODE, "publishedPostWithTags1981", "Hello world", true);
final BlogPostInfo blogPost1991 = BLOG_SERVICE.createBlogPost(BLOG_CONTAINER_NODE, "publishedPostWithTags1991", "Hello world", true);
TAGGING_SERVICE.addTags(blogPost1971.getNodeRef(), tags);
TAGGING_SERVICE.addTags(blogPost1981.getNodeRef(), tags);
TAGGING_SERVICE.addTags(blogPost1991.getNodeRef(), tags);
testNodesToTidy.add(blogPost1971.getNodeRef());
testNodesToTidy.add(blogPost1981.getNodeRef());
testNodesToTidy.add(blogPost1991.getNodeRef());
// We need to 'cheat' and set the nodes' cm:published dates to specific values.
NODE_SERVICE.setProperty(blogPost1971.getNodeRef(), ContentModel.PROP_PUBLISHED, _1971);
NODE_SERVICE.setProperty(blogPost1981.getNodeRef(), ContentModel.PROP_PUBLISHED, _1981);
NODE_SERVICE.setProperty(blogPost1991.getNodeRef(), ContentModel.PROP_PUBLISHED, _1991);
result.put(1971, blogPost1971.getNodeRef());
result.put(1981, blogPost1981.getNodeRef());
result.put(1991, blogPost1991.getNodeRef());
return result;
}
});
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@SuppressWarnings("deprecation")
@Override
public Void execute() throws Throwable
{
// Quick sanity check: Did our cheating with the cm:created dates work?
assertEquals("Incorrect published date", 71, ((Date)NODE_SERVICE.getProperty(blogPosts.get(1971), ContentModel.PROP_PUBLISHED)).getYear());
PagingRequest pagingReq = new PagingRequest(0, 10, null);
final RangedDateProperty publishedBefore1980 = new RangedDateProperty(null, _1980, ContentModel.PROP_PUBLISHED);
final RangedDateProperty publishedAfter1980 = new RangedDateProperty(_1980, null, ContentModel.PROP_PUBLISHED);
final RangedDateProperty publishedBetween1975And1985 = new RangedDateProperty(_1975, _1985, ContentModel.PROP_PUBLISHED);
List<ChildAssociationRef> children = NODE_SERVICE.getChildAssocs(BLOG_CONTAINER_NODE);
for (ChildAssociationRef child : children)
{
Map<QName, Serializable> props = NODE_SERVICE.getProperties(child.getChildRef());
System.out.println(props);
System.out.println("cm:name " + props.get(ContentModel.PROP_NAME));
System.out.println("cm:publ " + props.get(ContentModel.PROP_PUBLISHED));
System.out.println();
}
// Find all
PagingResults<BlogPostInfo> pagedResults = BLOG_SERVICE.findBlogPosts(BLOG_CONTAINER_NODE, null, null, pagingReq);
assertEquals("Wrong number of blog posts", 3, pagedResults.getPage().size());
Set<NodeRef> recoveredBlogNodes = new HashSet<NodeRef>();
for (BlogPostInfo bpi : pagedResults.getPage())
{
recoveredBlogNodes.add(bpi.getNodeRef());
}
assertTrue("Missing expected BlogPost NodeRef 71", recoveredBlogNodes.contains(blogPosts.get(1971)));
assertTrue("Missing expected BlogPost NodeRef 81", recoveredBlogNodes.contains(blogPosts.get(1981)));
assertTrue("Missing expected BlogPost NodeRef 91", recoveredBlogNodes.contains(blogPosts.get(1991)));
// Find posts before date
pagedResults = BLOG_SERVICE.findBlogPosts(BLOG_CONTAINER_NODE, publishedBefore1980, null, pagingReq);
assertEquals("Wrong blog post count", 1, pagedResults.getPage().size());
NodeRef blogNode = pagedResults.getPage().get(0).getNodeRef();
assertEquals("Incorrect NodeRef.", blogNode, blogPosts.get(1971));
List<String> recoveredTags = TAGGING_SERVICE.getTags(blogNode);
assertEquals("Incorrect tags.", tags, recoveredTags);
// Find posts after date
pagedResults = BLOG_SERVICE.findBlogPosts(BLOG_CONTAINER_NODE, publishedAfter1980, "hello", pagingReq);
assertEquals("Wrong blog post count", 2, pagedResults.getPage().size());
blogNode = pagedResults.getPage().get(0).getNodeRef();
assertEquals("Incorrect NodeRef.", blogNode, blogPosts.get(1991));
// Find posts between dates
pagedResults = BLOG_SERVICE.findBlogPosts(BLOG_CONTAINER_NODE, publishedBetween1975And1985, "hello", pagingReq);
assertEquals("Wrong blog post count", 1, pagedResults.getPage().size());
blogNode = pagedResults.getPage().get(0).getNodeRef();
assertEquals("Incorrect NodeRef.", blogNode, blogPosts.get(1981));
return null;
}
});
}
/**
* This test uses two different users to create draft and internally published blog posts.
* Then it ensures that each user sees the correct posts when they retrieve them from the service.

View File

@@ -23,9 +23,12 @@ import java.util.Date;
import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.blog.BlogIntegrationService;
import org.alfresco.repo.security.permissions.PermissionCheckValue;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.QName;
/**
* The Blog Service handles the management (CRUD) of Alfresco blog data, namely the blog posts which are
@@ -112,9 +115,12 @@ public interface BlogService
PagingResults<BlogPostInfo> getMyDraftsAndAllPublished(NodeRef blogContainerNode, Date fromDate, Date toDate, PagingRequest pagingReq);
/**
* Finds blog posts by the specified user tagged with the given tag string.
* Finds blog posts by the specified user tagged with the given tag string. This method allows date ranges to be applied to any valid
* {@link DataTypeDefinition#DATE} or {@link DataTypeDefinition#DATETIME} property. Examples include {@link ContentModel#PROP_CREATED} or
* {@link ContentModel#PROP_PUBLISHED}.
*
* @param blogContainerNode the container node for blog posts (under the site).
* @param dateRange a {@link RangedDateProperty} parameter object. Can be null.
* @param tag tag string.
* @param pagingReq an object defining the paging parameters for the result set.
*
@@ -122,7 +128,7 @@ public interface BlogService
*
* @see SiteService#getContainer(String, String) to retrieve the blogContainerNode
*/
PagingResults<BlogPostInfo> findTaggedBlogPosts(NodeRef blogContainerNode, String tag, PagingRequest pagingReq);
PagingResults<BlogPostInfo> findBlogPosts(NodeRef blogContainerNode, RangedDateProperty dateRange, String tag, PagingRequest pagingReq);
/**
* Returns true if the specified blog-post node is a 'draft' blog post.
@@ -166,4 +172,42 @@ public interface BlogService
return name;
}
}
/**
* A simple data object for expressing a date range search parameter.
*/
public class RangedDateProperty
{
private final Date fromDate;
private final Date toDate;
private final QName dateProperty;
/**
* Constructs a ConstrainedDateProperty object.
* @param fromDate the start date for the range (can be null for unbounded lower)
* @param toDate the end date for the range (can be null for unbounded upper)
* @param dateProperty the Alfresco node property which is to be checked against the range. (must be a valid date or datetime property)
*/
public RangedDateProperty(Date fromDate, Date toDate, QName dateProperty)
{
this.fromDate = fromDate;
this.toDate = toDate;
this.dateProperty = dateProperty;
}
public Date getFromDate()
{
return fromDate;
}
public Date getToDate()
{
return toDate;
}
public QName getDateProperty()
{
return dateProperty;
}
}
}