diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index ddd75d278a..6ee8bb0889 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -435,7 +435,6 @@ Rating d:float true - true true true @@ -446,7 +445,6 @@ Rating Scheme d:text true - true true true @@ -457,7 +455,6 @@ Rated at d:datetime true - true true true diff --git a/source/java/org/alfresco/repo/rating/RatingNodeProperties.java b/source/java/org/alfresco/repo/rating/RatingNodeProperties.java deleted file mode 100644 index ccf2077374..0000000000 --- a/source/java/org/alfresco/repo/rating/RatingNodeProperties.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2005-2010 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.rating; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.rating.RatingServiceException; -import org.alfresco.service.namespace.QName; - -/** - * The cm:rating content type defines the properties which are used in - * managing user-applied ratings. See contentModel.xml for details. - *

- * Briefly, these properties are multi-valued where a 'slice' through the property - * set constitutes an individual user-applied rating. So if we were to take the value - * at index n of each multi-valued property, that would represent the - * score, appliedAt and scheme for one rating. - *

- * This class simplifies and centralises the handling of these property sets. - * - * @author Neil McErlean - * @since 3.4 - */ -public class RatingNodeProperties -{ - // These lists are declared as the concrete type 'ArrayList' as they must implement Serializable - private final ArrayList schemes; - private final ArrayList scores; - private final ArrayList dates; - - public RatingNodeProperties(List schemes, List scores, List dates) - { - // No null lists. - if (schemes == null) schemes = new ArrayList(); - if (scores == null) scores = new ArrayList(); - if (dates == null) dates = new ArrayList(); - - // All lists must be the same length. - if (scores.size() != schemes.size() || dates.size() != schemes.size()) - { - throw new RatingServiceException("Rating node properties have unequal list lengths: " - + schemes.size() + " " + scores.size() + " " + dates.size()); - } - - // Copy all these data to ensure no leakage of this class' state into the original properties. - this.schemes = new ArrayList(schemes.size()); - for (String s : schemes) - this.schemes.add(s); - - this.scores = new ArrayList(scores.size()); - for (Float i : scores) - this.scores.add(i); - - this.dates = new ArrayList(dates.size()); - // We can't copy Dates straight over as Date objects are mutable, so clone. - for (Date d : dates) - this.dates.add((Date)d.clone()); - } - - /** - * This factory method creates a new {@link RatingNodeProperties} from the specified - * properties map. - * @param props - * @return - */ - @SuppressWarnings("unchecked") - public static RatingNodeProperties createFrom(Map props) - { - List schemes = (List) props.get(ContentModel.PROP_RATING_SCHEME); - List scores = (List)props.get(ContentModel.PROP_RATING_SCORE); - List dates = (List) props.get(ContentModel.PROP_RATED_AT); - - return new RatingNodeProperties(schemes, scores, dates); - } - - /** - * This method returns the number of ratings currently held by the multivalued properties. - * @return the number of ratings. - */ - public int size() - { - return this.schemes.size(); - } - - /** - * This method gets the {@link Rating} for the specified index. - * @param index - * @return - */ - public RatingStruct getRatingAt(int index) - { - String scheme = this.schemes.get(index); - float score = this.scores.get(index); - Date d = this.dates.get(index); - return new RatingStruct(scheme, score, d); - } - - /** - * This method appends a new rating in the specified schemeName. The ratedAt date - * will be set to 'now'. - * - * @param schemeName the scheme name. - * @param score the score. - */ - public void appendRating(String schemeName, float score) - { - this.schemes.add(schemeName); - this.scores.add(score); - this.dates.add(new Date()); - } - - /** - * This method sets the rating at the specified index. - * Note that to persist these changes, {@link RatingNodeProperties#toNodeProperties()} - * can be called to retrieve a property map which should then be saved via the - * {@link NodeService} in the usual way. - *

- * The ratedAt property will be automatically set to 'now'. - * @param index the index at which the change is to be made. - * @param scheme the new rating scheme name. - * @param score the new rating score. - */ - public void setRatingAt(int index, String scheme, float score) - { - this.schemes.set(index, scheme); - this.scores.set(index, score); - this.dates.set(index, new Date()); - } - - /** - * This method removes the rating at the specified index. - * @param index - * @return the removed rating data. - */ - public RatingStruct removeRatingAt(int index) - { - String removedScheme = this.schemes.remove(index); - float removedScore = this.scores.remove(index); - Date removedDate = this.dates.remove(index); - - return new RatingStruct(removedScheme, removedScore, removedDate); - } - - /** - * This method returns all ratings as a List of {@link RatingStruct} objects. - * @return - */ - public List getAllRatings() - { - List result = new ArrayList(schemes.size()); - for (int i = 0; i < schemes.size(); i++) - { - result.add(new RatingStruct(schemes.get(i), scores.get(i), dates.get(i))); - } - return result; - } - - /** - * This method gets the rating which has the specified schemeName. There should only - * be one such rating. - * @param schemeName - * @return the requested {@link RatingStruct} if there is one, else null. - */ - public RatingStruct getRating(String schemeName) - { - for (int i = 0; i < schemes.size(); i ++) - { - if (schemes.get(i).equals(schemeName)) - { - return new RatingStruct(schemes.get(i), scores.get(i), dates.get(i)); - } - } - return null; - } - - /** - * This method returns the index of the rating with the specified scheme name, if there - * is such a rating. - * @param schemeName - * @return the index of the specified rating if there is one, else -1. - */ - public int getIndexOfRating(String schemeName) - { - for (int i = 0; i < schemes.size(); i ++) - { - if (schemes.get(i).equals(schemeName)) - { - return i; - } - } - return -1; - } - - /** - * This method converts this {@link RatingNodeProperties} object into a Map of - * properties consistent with the Alfresco ratings content model. See contentModel.xml. - * These can then be set in the database via the {@link NodeService} in the usual way. - * @return - */ - public Map toNodeProperties() - { - Map results = new HashMap(); - results.put(ContentModel.PROP_RATING_SCHEME, this.schemes); - results.put(ContentModel.PROP_RATING_SCORE, this.scores); - results.put(ContentModel.PROP_RATED_AT, this.dates); - - return results; - } - - /** - * A simple struct class to help in handling the related properties within a cm:rating. - * @author Neil Mc Erlean. - * - */ - public class RatingStruct - { - private String scheme; - private float score; - private Date date; - - public RatingStruct(String scheme, float score, Date d) - { - RatingStruct.this.scheme = scheme; - RatingStruct.this.score = score; - RatingStruct.this.date = d; - } - - public String getScheme() - { - return scheme; - } - - public float getScore() - { - return score; - } - - public Date getDate() - { - return date; - } - - @Override - public boolean equals(Object thatObj) - { - if (thatObj == null || thatObj.getClass().equals(RatingStruct.this.getClass()) == false) - { - return false; - } - RatingStruct that = (RatingStruct)thatObj; - return RatingStruct.this.scheme.equals(that.scheme) && - RatingStruct.this.score == that.score && - RatingStruct.this.date.equals(that.date); - } - - @Override - public int hashCode() - { - return scheme.hashCode() + (int)(7 * score) + 11 * date.hashCode(); - } - - @Override - public String toString() - { - StringBuilder msg = new StringBuilder(); - msg.append(RatingStruct.this.getClass().getSimpleName()) - .append(" '").append(scheme).append("' ") - .append(score).append(" ").append(date); - return msg.toString(); - } -} -} diff --git a/source/java/org/alfresco/repo/rating/RatingNodePropertiesTest.java b/source/java/org/alfresco/repo/rating/RatingNodePropertiesTest.java deleted file mode 100644 index efb8030079..0000000000 --- a/source/java/org/alfresco/repo/rating/RatingNodePropertiesTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2005-2010 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.rating; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.rating.RatingNodeProperties.RatingStruct; -import org.alfresco.service.namespace.QName; -import org.junit.Before; -import org.junit.Test; - -/** - * Unit Test class for {@link RatingNodeProperties}. - * - * @author Neil Mc Erlean - * @since 3.4 - */ -public class RatingNodePropertiesTest -{ - private static final String LIKE = "like"; - private static final String FIVESTAR = "fivestar"; - - // These are declared as ArrayLists in order to be Serializable. - private ArrayList testSchemes; - private ArrayList testScores; - private ArrayList testDates; - private RatingNodeProperties testProps; - - @Before public void initTestData() - { - testSchemes = new ArrayList(); - testScores = new ArrayList(); - testDates = new ArrayList(); - // These correspond to two ratings: - // '1' in the 'like' scheme at 'now'. - // '5' in the 'fivestar' scheme at 'now'. - testSchemes.add(LIKE); - testSchemes.add(FIVESTAR); - - testScores.add(1.0f); - testScores.add(5.0f); - - testDates.add(new Date()); - testDates.add(new Date()); - - Map alfrescoStyleProps = new HashMap(); - alfrescoStyleProps.put(ContentModel.PROP_RATING_SCHEME, testSchemes); - alfrescoStyleProps.put(ContentModel.PROP_RATING_SCORE, testScores); - alfrescoStyleProps.put(ContentModel.PROP_RATED_AT, testDates); - - testProps = RatingNodeProperties.createFrom(alfrescoStyleProps); - - } - - /** - * This test method checks that constructing a RatingNodeProperties with null-valued - * property lists works. - */ - @Test public void noNullPropertyLists() throws Exception - { - List schemes = null; - List scores = null; - List dates = null; - RatingNodeProperties nullProps = new RatingNodeProperties(schemes, scores, dates); - - assertEquals(0, nullProps.size()); - } - - @Test public void constructAndAccessRatings() throws Exception - { - assertEquals(2, testProps.size()); - assertEquals(2, testProps.getAllRatings().size()); - - assertEquals(0, testProps.getIndexOfRating(LIKE)); - assertEquals(-1, testProps.getIndexOfRating("noSuchScheme")); - - final RatingStruct firstRating = testProps.getRatingAt(0); - assertEquals(LIKE, firstRating.getScheme()); - assertEquals(1.0f, firstRating.getScore(), 0.1f); - final Date recoveredLikeDate = firstRating.getDate(); - assertNotNull(recoveredLikeDate); - - final RatingStruct secondRating = testProps.getRatingAt(1); - assertEquals(FIVESTAR, secondRating.getScheme()); - assertEquals(5, secondRating.getScore(), 0.1f); - final Date recoveredSecondDate = secondRating.getDate(); - assertNotNull(recoveredSecondDate); - - RatingStruct l = testProps.getRating(LIKE); - assertNotNull(l); - assertEquals(LIKE, l.getScheme()); - assertEquals(1, l.getScore(), 0.1f); - assertEquals(recoveredLikeDate, l.getDate()); - } - - @Test public void appendRating() - { - // Check all is right before we start - assertEquals(2, testProps.size()); - - testProps.appendRating("appended", 10); - assertEquals(3, testProps.size()); - - assertNotNull(testProps.getRating(LIKE)); - assertNotNull(testProps.getRating(FIVESTAR)); - assertNotNull(testProps.getRating("appended")); - } - - @Test public void removeRating() - { - // Check all is right before we start - assertEquals(2, testProps.size()); - - // Remove the first rating - should be 'like' - - testProps.removeRatingAt(0); - assertEquals(1, testProps.size()); - - // Now 'like' should be gone, but 'fivestar' should still be there. - assertNull(testProps.getRating(LIKE)); - assertNotNull(testProps.getRating(FIVESTAR)); - } - - @Test public void replaceRating() - { - // Check all is right before we start - assertEquals(2, testProps.size()); - - // Replace the first rating - should be 'like' - - // There's no such rating scheme as 'foo' but for this unit test it doesn't matter. - testProps.setRatingAt(0, "foo", 42); - assertEquals(2, testProps.size()); - - // Now 'like' should be replaced by 'foo'. - assertNull(testProps.getRating(LIKE)); - assertNotNull(testProps.getRating(FIVESTAR)); - final RatingStruct fooRating = testProps.getRating("foo"); - assertNotNull(fooRating); - assertEquals(42, fooRating.getScore(), 0.1f); - } - - @SuppressWarnings("unchecked") - @Test public void extractAlfrescoNodeProperties() - { - Map alfProps = this.testProps.toNodeProperties(); - assertNotNull(alfProps); - final int numberOfProperties = 3; - assertEquals(numberOfProperties, alfProps.size()); - final List ratingSchemes = (List)alfProps.get(ContentModel.PROP_RATING_SCHEME); - final List ratingScores = (List)alfProps.get(ContentModel.PROP_RATING_SCORE); - final List ratingDates = (List)alfProps.get(ContentModel.PROP_RATED_AT); - final int numberOfRatings = 2; - assertEquals(numberOfRatings, ratingSchemes.size()); - assertEquals(numberOfRatings, ratingScores.size()); - assertEquals(numberOfRatings, ratingDates.size()); - assertEquals(Arrays.asList(new String[]{LIKE, FIVESTAR}), ratingSchemes); - assertEquals(Arrays.asList(new Float[]{1.0f, 5.0f}), ratingScores); - // No Date checks - } -} diff --git a/source/java/org/alfresco/repo/rating/RatingServiceImpl.java b/source/java/org/alfresco/repo/rating/RatingServiceImpl.java index 980333d7e6..9fe52263a0 100644 --- a/source/java/org/alfresco/repo/rating/RatingServiceImpl.java +++ b/source/java/org/alfresco/repo/rating/RatingServiceImpl.java @@ -20,15 +20,12 @@ package org.alfresco.repo.rating; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.repo.rating.RatingNodeProperties.RatingStruct; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.rating.Rating; import org.alfresco.service.cmr.rating.RatingScheme; @@ -148,17 +145,16 @@ public class RatingServiceImpl implements RatingService { // There are no previous ratings from this user, so we create a new cm:rating child node. - // These are multivalued properties. Map ratingProps = new HashMap(); - ratingProps.put(ContentModel.PROP_RATING_SCORE, toSerializableList(new Float[]{rating})); - ratingProps.put(ContentModel.PROP_RATED_AT, toSerializableList(new Date[]{new Date()})); - ratingProps.put(ContentModel.PROP_RATING_SCHEME, toSerializableList(new String[]{ratingSchemeName})); + ratingProps.put(ContentModel.PROP_RATING_SCORE, rating); + ratingProps.put(ContentModel.PROP_RATED_AT, new Date()); + ratingProps.put(ContentModel.PROP_RATING_SCHEME, ratingSchemeName); nodeService.createNode(targetNode, ContentModel.ASSOC_RATINGS, assocQName, ContentModel.TYPE_RATING, ratingProps); } else { - // There are previous ratings by this user. Things are a little more complex. + // There are previous ratings by this user. if (myRatingChildren.size() > 1 && log.isDebugEnabled()) { log.debug(""); @@ -166,27 +162,33 @@ public class RatingServiceImpl implements RatingService NodeRef myPreviousRatingsNode = myRatingChildren.get(0).getChildRef(); Map existingProps = nodeService.getProperties(myPreviousRatingsNode); - RatingNodeProperties ratingProps = RatingNodeProperties.createFrom(existingProps); + String existingRatingScheme = (String)existingProps.get(ContentModel.PROP_RATING_SCHEME); - // If the schemes list already contains an entry matching the rating we're setting - // we need to delete it and then delete the score and date at the corresponding indexes. - int indexOfExistingRating = ratingProps.getIndexOfRating(ratingSchemeName); - if (indexOfExistingRating != -1) + // If it's a re-rating in the existing scheme, replace. + if (ratingScheme.getName().equals(existingRatingScheme)) { - ratingProps.removeRatingAt(indexOfExistingRating); + Map ratingProps = new HashMap(); + ratingProps.put(ContentModel.PROP_RATING_SCHEME, ratingSchemeName); + ratingProps.put(ContentModel.PROP_RATING_SCORE, rating); + ratingProps.put(ContentModel.PROP_RATED_AT, new Date()); + + nodeService.setProperties(myPreviousRatingsNode, ratingProps); + } + // But if it's a new rating in a different scheme, we don't support this scenario. + else + { + StringBuilder msg = new StringBuilder(); + msg.append("Cannot apply rating ") + .append(rating).append(" [") + .append(ratingSchemeName).append("] to node ") + .append(targetNode).append(". Already rated in ") + .append(existingRatingScheme); + + throw new RatingServiceException(msg.toString()); } - - ratingProps.appendRating(ratingSchemeName, rating); - - nodeService.setProperties(myPreviousRatingsNode, ratingProps.toNodeProperties()); } } - private Serializable toSerializableList(Object[] array) - { - return (Serializable)Arrays.asList(array); - } - /* * (non-Javadoc) * @see org.alfresco.service.cmr.rating.RatingService#getRatingByCurrentUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) @@ -207,27 +209,26 @@ public class RatingServiceImpl implements RatingService return null; } - // Take the last node pertaining to the current user. - ChildAssociationRef lastChild = ratingChildren.get(ratingChildren.size() - 1); - Map properties = nodeService.getProperties(lastChild.getChildRef()); + // Take the node pertaining to the current user. + ChildAssociationRef ratingNodeAssoc = ratingChildren.get(0); + Map properties = nodeService.getProperties(ratingNodeAssoc.getChildRef()); // Find the index of the rating scheme we're interested in. - RatingNodeProperties ratingProps = RatingNodeProperties.createFrom(properties); - int index = ratingProps.getIndexOfRating(ratingSchemeName); - if (index == -1) + String existingRatingScheme = (String)properties.get(ContentModel.PROP_RATING_SCHEME); + if (existingRatingScheme.equals(ratingSchemeName) == false) { // There is no rating in this scheme by the specified user. return null; } else { - // There is a rating and the associated data are at the index'th place in each multivalued property. - RatingStruct ratingStruct = ratingProps.getRatingAt(index); + Float existingRatingScore = (Float)properties.get(ContentModel.PROP_RATING_SCORE); + Date existingRatingDate = (Date)properties.get(ContentModel.PROP_RATED_AT); - Rating result = new Rating(getRatingScheme(ratingStruct.getScheme()), - ratingStruct.getScore(), + Rating result = new Rating(getRatingScheme(existingRatingScheme), + existingRatingScore, user, - ratingStruct.getDate()); + existingRatingDate); return result; } } @@ -243,39 +244,29 @@ public class RatingServiceImpl implements RatingService return removeRating(targetNode, ratingScheme, currentUser); } - private Rating removeRating(NodeRef targetNode, String ratingSchemeName, String user) + private Rating removeRating(NodeRef targetNode, String ratingSchemeName, final String user) { List ratingChildren = getRatingNodeChildren(targetNode, user); if (ratingChildren.isEmpty()) { - // There are no ratings by any user. return null; } - // Take the last node pertaining to the specified user. - ChildAssociationRef lastChild = ratingChildren.get(ratingChildren.size() - 1); + ChildAssociationRef lastChild = ratingChildren.get(0); Map properties = nodeService.getProperties(lastChild.getChildRef()); - - // Find the index of the rating scheme we're interested in. - RatingNodeProperties ratingProps = RatingNodeProperties.createFrom(properties); - int index = ratingProps.getIndexOfRating(ratingSchemeName); - if (index == -1) + Rating result = null; + // If the rating is for the specified scheme delete it. + // Get the scheme name and check it. + if (ratingSchemeName.equals(properties.get(ContentModel.PROP_RATING_SCHEME))) { - // There is no rating in this scheme by the specified user. - return null; - } - else - { - // There is a rating and the associated data are at the index'th place in each property. - RatingStruct removed = ratingProps.removeRatingAt(index); - - // Now apply the properties with one deleted. - Map props = ratingProps.toNodeProperties(); - nodeService.setProperties(lastChild.getChildRef(), props); + Float score = (Float) properties.get(ContentModel.PROP_RATING_SCORE); + Date date = (Date)properties.get(ContentModel.PROP_RATED_AT); - return new Rating(this.getRatingScheme(removed.getScheme()), - removed.getScore(), user, removed.getDate()); + nodeService.deleteNode(lastChild.getChildRef()); + result = new Rating(getRatingScheme(ratingSchemeName), score, user, date); } + + return result; } /* @@ -284,7 +275,7 @@ public class RatingServiceImpl implements RatingService */ public float getTotalRating(NodeRef targetNode, String ratingSchemeName) { - //TODO Performance improvement? : put node rating total/count/average into a multi-valued + //TODO Performance improvement? : put node rating total/count/average into a // property in the db. List ratingsNodes = this.getRatingNodeChildren(targetNode, null); @@ -295,13 +286,10 @@ public class RatingServiceImpl implements RatingService float result = 0; for (ChildAssociationRef ratingsNode : ratingsNodes) { - List ratings = getRatingsFrom(ratingsNode.getChildRef()); - for (Rating rating : ratings) + Rating rating = getRatingFrom(ratingsNode.getChildRef()); + if (rating.getScheme().getName().equals(ratingSchemeName)) { - if (rating.getScheme().getName().equals(ratingSchemeName)) - { - result += rating.getScore(); - } + result += rating.getScore(); } } return result; @@ -319,14 +307,11 @@ public class RatingServiceImpl implements RatingService float ratingTotal = 0; for (ChildAssociationRef ratingsNode : ratingsNodes) { - List ratings = getRatingsFrom(ratingsNode.getChildRef()); - for (Rating rating : ratings) + Rating rating = getRatingFrom(ratingsNode.getChildRef()); + if (rating.getScheme().getName().equals(ratingSchemeName)) { - if (rating.getScheme().getName().equals(ratingSchemeName)) - { - ratingCount++; - ratingTotal += rating.getScore(); - } + ratingCount++; + ratingTotal += rating.getScore(); } } if (ratingCount == 0) @@ -350,13 +335,10 @@ public class RatingServiceImpl implements RatingService int result = 0; for (ChildAssociationRef ratingsNode : ratingsNodes) { - List ratings = getRatingsFrom(ratingsNode.getChildRef()); - for (Rating rating : ratings) + Rating rating = getRatingFrom(ratingsNode.getChildRef()); + if (rating.getScheme().getName().equals(ratingSchemeName)) { - if (rating.getScheme().getName().equals(ratingSchemeName)) - { - result++; - } + result++; } } return result; @@ -390,13 +372,11 @@ public class RatingServiceImpl implements RatingService } /** - * This method returns a List of {@link Rating} objects for the specified cm:rating - * node. As it's one ratingNode the results will be form one user, but will represent - * 0..n schemes. + * This method returns a {@link Rating} object for the specified cm:rating node. * @param ratingNode * @return */ - private List getRatingsFrom(NodeRef ratingNode) + private Rating getRatingFrom(NodeRef ratingNode) { // The appliedBy is encoded in the parent assoc qname. // It will be the same user for all ratings in this node. @@ -404,16 +384,12 @@ public class RatingServiceImpl implements RatingService String appliedBy = parentAssoc.getQName().getLocalName(); Map properties = nodeService.getProperties(ratingNode); - RatingNodeProperties ratingProps = RatingNodeProperties.createFrom(properties); - List result = new ArrayList(ratingProps.size()); - for (int i = 0; i < ratingProps.size(); i++) - { - final String schemeName = ratingProps.getRatingAt(i).getScheme(); - RatingScheme scheme = getRatingScheme(schemeName); - result.add(new Rating(scheme, ratingProps.getRatingAt(i).getScore(), - appliedBy, ratingProps.getRatingAt(i).getDate())); - } + final String schemeName = (String)properties.get(ContentModel.PROP_RATING_SCHEME); + final Float score = (Float)properties.get(ContentModel.PROP_RATING_SCORE); + final Date ratedAt = (Date)properties.get(ContentModel.PROP_RATED_AT); + RatingScheme scheme = getRatingScheme(schemeName); + Rating result = new Rating(scheme, score, appliedBy, ratedAt); return result; } } diff --git a/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java b/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java index 98879c7a1e..1d2fa76cb4 100644 --- a/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java @@ -20,7 +20,6 @@ package org.alfresco.repo.rating; import java.io.Serializable; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -179,7 +178,7 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest fail("Illegal rating " + illegalRating + " should have caused exception."); } - public void testApplyUpdateDeleteRatings_SingleUserMultipleSchemes() throws Exception + public void testApplyUpdateDeleteRatings() throws Exception { // We'll do all this as user 'UserOne'. AuthenticationUtil.setFullyAuthenticatedUser(USER_TWO); @@ -189,10 +188,8 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest assertNull("Expected a null rating,", nullRating); assertNull("Expected a null remove result.", ratingService.removeRatingByCurrentUser(testDoc_Admin, LIKES_SCHEME_NAME)); - final float likesScore = 1; final float fiveStarScore = 5; - ratingService.applyRating(testDoc_Admin, likesScore, LIKES_SCHEME_NAME); ratingService.applyRating(testDoc_Admin, fiveStarScore, FIVE_STAR_SCHEME_NAME); // Some basic node structure tests. @@ -213,16 +210,8 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest // Now to check the persisted ratings data are ok. - Rating likeRating = ratingService.getRatingByCurrentUser(testDoc_Admin, LIKES_SCHEME_NAME); - Rating fiveStarRating = ratingService.getRatingByCurrentUser(testDoc_Admin, FIVE_STAR_SCHEME_NAME); - assertNotNull("'like' rating was null.", likeRating); - assertEquals("Wrong score for rating", likesScore, likeRating.getScore()); - assertEquals("Wrong user for rating", AuthenticationUtil.getFullyAuthenticatedUser(), likeRating.getAppliedBy()); - final Date likeRatingAppliedAt = likeRating.getAppliedAt(); - assertDateIsCloseToNow(likeRatingAppliedAt); - assertNotNull("'5*' rating was null.", fiveStarRating); assertEquals("Wrong score for rating", fiveStarScore, fiveStarRating.getScore()); assertEquals("Wrong user for rating", AuthenticationUtil.getFullyAuthenticatedUser(), fiveStarRating.getAppliedBy()); @@ -248,27 +237,13 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest // Now to check the updated ratings data are ok. Rating updatedFiveStarRating = ratingService.getRatingByCurrentUser(testDoc_Admin, FIVE_STAR_SCHEME_NAME); - // 'like' rating data should be unchanged. - assertNotNull("'like' rating was null.", likeRating); - assertEquals("Wrong score for rating", likesScore, likeRating.getScore()); - assertEquals("Wrong user for rating", AuthenticationUtil.getFullyAuthenticatedUser(), likeRating.getAppliedBy()); - assertEquals("Wrong date for rating", likeRatingAppliedAt, likeRating.getAppliedAt()); - - // But these 'five star' data should be changed - new score, new date + // 'five star' data should be changed - new score, new date assertNotNull("'5*' rating was null.", updatedFiveStarRating); assertEquals("Wrong score for rating", updatedFiveStarScore, updatedFiveStarRating.getScore()); assertEquals("Wrong user for rating", AuthenticationUtil.getFullyAuthenticatedUser(), updatedFiveStarRating.getAppliedBy()); assertTrue("five star rating date was unchanged.", fiveStarRatingAppliedAt.equals(updatedFiveStarRating.getAppliedAt()) == false); assertDateIsCloseToNow(updatedFiveStarRating.getAppliedAt()); - // Now we'll delete the 'likes' rating. - Rating deletedLikesRating = ratingService.removeRatingByCurrentUser(testDoc_Admin, LIKES_SCHEME_NAME); - // 'like' rating data should be unchanged. - assertNotNull("'like' rating was null.", deletedLikesRating); - assertEquals("Wrong score for rating", likesScore, deletedLikesRating.getScore()); - assertEquals("Wrong user for rating", AuthenticationUtil.getFullyAuthenticatedUser(), deletedLikesRating.getAppliedBy()); - assertEquals("Wrong date for rating", likeRatingAppliedAt, deletedLikesRating.getAppliedAt()); - // And delete the 'five star' rating. Rating deletedStarRating = ratingService.removeRatingByCurrentUser(testDoc_Admin, FIVE_STAR_SCHEME_NAME); // 'five star' rating data should be unchanged. @@ -279,7 +254,6 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest // And the deleted ratings should be gone. assertNull("5* rating not null.", ratingService.getRatingByCurrentUser(testDoc_Admin, FIVE_STAR_SCHEME_NAME)); - assertNull("likes rating not null", ratingService.getRatingByCurrentUser(testDoc_Admin, LIKES_SCHEME_NAME)); } /** @@ -299,6 +273,64 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest // assertTrue("Date was not within " + millisTolerance + "ms of 'now'.", now.getTime() - d.getTime() < millisTolerance); } + public void testOneUserRatesAndRerates() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + ratingService.applyRating(testDoc_Admin, 1.0f, FIVE_STAR_SCHEME_NAME); + + // A new score in the same rating scheme by the same user should replace the previous score. + ratingService.applyRating(testDoc_Admin, 2.0f, FIVE_STAR_SCHEME_NAME); + + float meanRating = ratingService.getAverageRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong mean rating.", 2f, meanRating); + + float totalRating = ratingService.getTotalRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong total rating.", 2.0f, totalRating); + + int ratingsCount = ratingService.getRatingsCount(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong ratings count.", 1, ratingsCount); + + // There should only be one rating child node under the rated node. + assertEquals("Wrong number of child nodes", 1 , nodeService.getChildAssocs(testDoc_Admin).size()); + } + + /** + * This test method ensures that if a single user attempts to rate a piece of content in two + * different rating schemes, then an exception should be thrown. + * @throws Exception + */ + public void testOneUserRatesInTwoSchemes() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + ratingService.applyRating(testDoc_Admin, 1.0f, FIVE_STAR_SCHEME_NAME); + + // A new score in a different rating scheme by the same user should fail. + boolean correctExceptionThrown = false; + try + { + ratingService.applyRating(testDoc_Admin, 2.0f, LIKES_SCHEME_NAME); + } catch (RatingServiceException expected) + { + correctExceptionThrown = true; + } + if (correctExceptionThrown == false) + { + fail("Expected exception not thrown."); + } + + float meanRating = ratingService.getAverageRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong mean rating.", 1f, meanRating); + + float totalRating = ratingService.getTotalRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong total rating.", 1f, totalRating); + + int ratingsCount = ratingService.getRatingsCount(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong ratings count.", 1, ratingsCount); + + // There should only be one rating child node under the rated node. + assertEquals("Wrong number of child nodes", 1 , nodeService.getChildAssocs(testDoc_Admin).size()); + } + public void testApplyRating_MultipleUsers() throws Exception { // 2 different users rating the same piece of content in the same rating scheme diff --git a/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java b/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java index c49d833762..ef69d05542 100644 --- a/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java +++ b/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java @@ -26,6 +26,7 @@ import org.alfresco.repo.jscript.ScriptNode; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.rating.Rating; import org.alfresco.service.cmr.rating.RatingService; +import org.alfresco.service.cmr.rating.RatingServiceException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -92,10 +93,13 @@ public class ScriptRatingService extends BaseScopableProcessorExtension * Applies the given rating to the specified node using the specified ratingScheme. * It is the responsibility of the caller to ensure that the rating scheme exists * and that the rating is within the limits defined for that scheme. + *

Furthermore, only one rating scheme per user per target node is supported. Any attempt + * by one user to apply a second rating in a different scheme will result in a {@link RatingServiceException}. * * @param node * @param rating * @param ratingSchemeName + * @throws RatingServiceException * @see ScriptRatingService#getMin(String) * @see ScriptRatingService#getMax(String) */ diff --git a/source/java/org/alfresco/service/cmr/rating/RatingService.java b/source/java/org/alfresco/service/cmr/rating/RatingService.java index 9845f069f5..69226bb200 100644 --- a/source/java/org/alfresco/service/cmr/rating/RatingService.java +++ b/source/java/org/alfresco/service/cmr/rating/RatingService.java @@ -64,13 +64,18 @@ public interface RatingService /** * This method applies the given rating to the specified target node. If a rating * from the current user in the specified scheme already exists, it will be replaced. + *

+ * Note that only one rating scheme per user per targetNode is supported at present. + * If a user attempts to apply a second rating in a different rating scheme to any given + * target node, a {@link RatingServiceException} will be thrown. * * @param targetNode the node to which the rating is to be applied. * @param rating the rating which is to be applied. * @param ratingSchemeName the name of the rating scheme to use. * * @throws RatingServiceException if the rating is not within the range defined by the named scheme - * or if the named scheme is not registered. + * or if the named scheme is not registered or if the rating would result + * in multiple ratings by the same user. * @see RatingService#getRatingSchemes() * @see RatingScheme */ @@ -91,6 +96,20 @@ public interface RatingService @NotAuditable int getRatingsCount(NodeRef targetNode, String ratingSchemeName); + /** + * This method gets the total accumulated rating score for + * the specified node in the specified {@link RatingScheme}. + * That is, the rating scores for all users for the specified + * node are summed to give the result. + * + * @param targetNode the node on which the rating total is sought. + * @param ratingScheme the rating scheme to use. + * + * @return the sum of all individual ratings applied to this node in the specified scheme. + * @see RatingService#getRatingSchemes() + * @see RatingScheme + */ + @NotAuditable float getTotalRating(NodeRef targetNode, String ratingSchemeName); /** @@ -100,6 +119,7 @@ public interface RatingService * @param ratingSchemeName the rating scheme name in which the average is defined. * @return the average (mean) value if there is one, else -1. */ + @NotAuditable float getAverageRating(NodeRef targetNode, String ratingSchemeName); /**