From f58f9c91c46fa8a501ed737fafb6498835a189cf Mon Sep 17 00:00:00 2001 From: Neil McErlean Date: Wed, 13 Jul 2011 15:33:32 +0000 Subject: [PATCH] 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 --- config/alfresco/blog-context.xml | 2 + .../alfresco/repo/blog/BlogServiceImpl.java | 99 ++++++++++++- .../repo/blog/BlogServiceImplTest.java | 132 ++++++++++++++++++ .../service/cmr/blog/BlogService.java | 48 ++++++- 4 files changed, 275 insertions(+), 6 deletions(-) diff --git a/config/alfresco/blog-context.xml b/config/alfresco/blog-context.xml index 040f104b72..e30f3f6362 100644 --- a/config/alfresco/blog-context.xml +++ b/config/alfresco/blog-context.xml @@ -63,6 +63,8 @@ + + diff --git a/source/java/org/alfresco/repo/blog/BlogServiceImpl.java b/source/java/org/alfresco/repo/blog/BlogServiceImpl.java index 20f35cbec1..856bd9a1f7 100644 --- a/source/java/org/alfresco/repo/blog/BlogServiceImpl.java +++ b/source/java/org/alfresco/repo/blog/BlogServiceImpl.java @@ -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 findTaggedBlogPosts( - NodeRef blogContainerNode, String tag, PagingRequest pagingReq) + public PagingResults 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(); + } } diff --git a/source/java/org/alfresco/repo/blog/BlogServiceImplTest.java b/source/java/org/alfresco/repo/blog/BlogServiceImplTest.java index 0711a62797..604081aee9 100644 --- a/source/java/org/alfresco/repo/blog/BlogServiceImplTest.java +++ b/source/java/org/alfresco/repo/blog/BlogServiceImplTest.java @@ -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 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>() + { + @Override + public Map execute() throws Throwable + { + Map result = new HashMap(); + + // 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() + { + @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 children = NODE_SERVICE.getChildAssocs(BLOG_CONTAINER_NODE); + for (ChildAssociationRef child : children) + { + Map 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 pagedResults = BLOG_SERVICE.findBlogPosts(BLOG_CONTAINER_NODE, null, null, pagingReq); + assertEquals("Wrong number of blog posts", 3, pagedResults.getPage().size()); + Set recoveredBlogNodes = new HashSet(); + 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 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. diff --git a/source/java/org/alfresco/service/cmr/blog/BlogService.java b/source/java/org/alfresco/service/cmr/blog/BlogService.java index bfe87fbc32..d4f072aa87 100644 --- a/source/java/org/alfresco/service/cmr/blog/BlogService.java +++ b/source/java/org/alfresco/service/cmr/blog/BlogService.java @@ -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 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 findTaggedBlogPosts(NodeRef blogContainerNode, String tag, PagingRequest pagingReq); + PagingResults 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; + } + } }