diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/rating/rating.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/rating.post.desc.xml new file mode 100644 index 0000000000..eaa82959ba --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/rating.post.desc.xml @@ -0,0 +1,22 @@ + + POST a rating to a NodeRef + + The rating consists of a score and a rating scheme name. The rating scheme will + define a minimum and a maximum allowed score and it is the responsibility of the + caller of this webscript to ensure that the scheme name is valid and that the posted + score is within the allowed range.
+ The rating will be applied using the fully authenticated user who makes the POST call.
+ The body of the post should be in the form, e.g.
+ {
+    "rating": 5,
+    "ratingScheme": "fiveStarRatingScheme"
+ }
+ ]]> +
+ /api/node/{store_type}/{store_id}/{id}/ratings + + user + required + internal +
\ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/rating/rating.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/rating.post.json.ftl new file mode 100644 index 0000000000..0e26e23ff6 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/rating.post.json.ftl @@ -0,0 +1,8 @@ +<#escape x as jsonUtils.encodeJSONString(x)> +{ + "data": + { + "ratedNodeUrl": "${ratedNode!""}" + } +} + \ No newline at end of file diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index b65f9590aa..f524c1ea73 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -753,6 +753,11 @@ parent="abstractRatingWebScript"> + + + diff --git a/source/java/org/alfresco/repo/web/scripts/rating/RatingPost.java b/source/java/org/alfresco/repo/web/scripts/rating/RatingPost.java index b0b0fdfde2..8b90d6fb20 100644 --- a/source/java/org/alfresco/repo/web/scripts/rating/RatingPost.java +++ b/source/java/org/alfresco/repo/web/scripts/rating/RatingPost.java @@ -18,21 +18,88 @@ */ package org.alfresco.repo.web.scripts.rating; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.HashMap; import java.util.Map; +import org.alfresco.service.cmr.rating.RatingScheme; +import org.alfresco.service.cmr.repository.NodeRef; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptRequest; /** - * @author unknown - * + * This class is the controller for the rating.post webscript. + * + * @author Neil McErlean + * @since 3.4 */ public class RatingPost extends AbstractRatingWebScript { + // Web script parameters. + private static final String RATING_SCHEME = "ratingScheme"; + private static final String RATING = "rating"; + private static final String RATED_NODE = "ratedNode"; + + // Url format + private final static String NODE_RATINGS_URL_FORMAT = "/api/node/{0}/ratings"; + @Override protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) { - return null; + Map model = new HashMap(); + + NodeRef nodeRefToBeRated = parseRequestForNodeRef(req); + + JSONObject json = null; + try + { + // read request json + json = new JSONObject(new JSONTokener(req.getContent().getContent())); + + // Check mandatory parameters. + if (json.has(RATING) == false) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "rating parameter missing when applying rating"); + } + if (json.has(RATING_SCHEME) == false) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "schemeName parameter missing when applying rating"); + } + + // Check that the scheme name actually exists + String schemeName = json.getString(RATING_SCHEME); + RatingScheme scheme = ratingService.getRatingScheme(schemeName); + if (scheme == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Unknown scheme name: " + schemeName); + } + + // Range checking of the rating score will be done within the RatingService. + // So we can just apply the rating. + int rating = json.getInt(RATING); + ratingService.applyRating(nodeRefToBeRated, rating, schemeName); + + // We'll return the URL to the ratings of the just-rated node. + String ratedNodeUrlFragment = nodeRefToBeRated.toString().replace("://", "/"); + String ratedNodeUrl = MessageFormat.format(NODE_RATINGS_URL_FORMAT, ratedNodeUrlFragment); + + model.put(RATED_NODE, ratedNodeUrl); + } + catch (IOException iox) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", iox); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je); + } + + return model; } } diff --git a/source/java/org/alfresco/repo/web/scripts/rating/RatingPut.java b/source/java/org/alfresco/repo/web/scripts/rating/RatingPut.java deleted file mode 100644 index 996e263a5a..0000000000 --- a/source/java/org/alfresco/repo/web/scripts/rating/RatingPut.java +++ /dev/null @@ -1,38 +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.web.scripts.rating; - -import java.util.Map; - -import org.springframework.extensions.webscripts.Cache; -import org.springframework.extensions.webscripts.Status; -import org.springframework.extensions.webscripts.WebScriptRequest; - -/** - * @author unknown - * - */ -public class RatingPut extends RatingPost -{ - @Override - protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) - { - return null; - } -} diff --git a/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java index b2f017a319..80efb4cd3f 100644 --- a/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java @@ -28,40 +28,57 @@ import org.alfresco.repo.web.scripts.BaseWebScriptTest; 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; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.PropertyMap; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONStringer; import org.json.JSONTokener; import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest; import org.springframework.extensions.webscripts.TestWebScriptServer.Response; public class RatingRestApiTest extends BaseWebScriptTest { - private final static String GET_RATINGS_URL_FORMAT = "/api/node/{0}/ratings"; + private static final String USER_ONE = "UserOne"; + private static final String USER_TWO = "UserTwo"; + + private final static String NODE_RATINGS_URL_FORMAT = "/api/node/{0}/ratings"; private final static String GET_RATING_DEFS_URL = "/api/rating/schemedefinitions"; private static final String APPLICATION_JSON = "application/json"; private NodeRef testNode; - protected NodeService nodeService; - protected Repository repositoryHelper; - protected RetryingTransactionHelper transactionHelper; + private MutableAuthenticationService authenticationService; + private NodeService nodeService; + private PersonService personService; + private Repository repositoryHelper; + private RetryingTransactionHelper transactionHelper; @Override protected void setUp() throws Exception { super.setUp(); + authenticationService = (MutableAuthenticationService) getServer().getApplicationContext().getBean("AuthenticationService"); nodeService = (NodeService) getServer().getApplicationContext().getBean("NodeService"); + personService = (PersonService) getServer().getApplicationContext().getBean("PersonService"); repositoryHelper = (Repository) getServer().getApplicationContext().getBean("repositoryHelper"); transactionHelper = (RetryingTransactionHelper)getServer().getApplicationContext().getBean("retryingTransactionHelper"); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); - // Create a test node which we will rate. It doesn't matter that it has no content. + // Create some users to rate each other's content + // and a test node which we will rate. + // It doesn't matter that it has no content. testNode = transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { public NodeRef execute() throws Throwable { + createUser(USER_ONE); + createUser(USER_TWO); + ChildAssociationRef result = nodeService.createNode(repositoryHelper.getCompanyHome(), ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS, ContentModel.TYPE_CONTENT, null); @@ -74,6 +91,9 @@ public class RatingRestApiTest extends BaseWebScriptTest public void tearDown() throws Exception { super.tearDown(); + + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { public Void execute() throws Throwable @@ -81,6 +101,8 @@ public class RatingRestApiTest extends BaseWebScriptTest if (testNode != null && nodeService.exists(testNode)) { nodeService.deleteNode(testNode); + deleteUser(USER_ONE); + deleteUser(USER_TWO); } return null; } @@ -88,9 +110,7 @@ public class RatingRestApiTest extends BaseWebScriptTest } //TODO test POST out-of-range. - //TODO test get my-ratings on node with mine & others' ratings. //TODO test GET average - //TODO test POST and PUT (same) public void testGetRatingSchemeDefinitions() throws Exception { @@ -100,9 +120,6 @@ public class RatingRestApiTest extends BaseWebScriptTest JSONObject jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); - - System.err.println(jsonRsp); - JSONObject dataObj = (JSONObject)jsonRsp.get("data"); assertNotNull("JSON 'data' object was null", dataObj); @@ -124,16 +141,13 @@ public class RatingRestApiTest extends BaseWebScriptTest public void testGetRatingsFromUnratedNodeRef() throws Exception { // GET ratings - String nodeUrl = testNode.toString().replace("://", "/"); - String ratingUrl = MessageFormat.format(GET_RATINGS_URL_FORMAT, nodeUrl); + String ratingUrl = getRatingUrl(testNode); final int expectedStatus = 200; Response rsp = sendRequest(new GetRequest(ratingUrl), expectedStatus); JSONObject jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); - System.err.println(jsonRsp); - JSONObject dataObj = (JSONObject)jsonRsp.get("data"); assertNotNull("JSON 'data' object was null", dataObj); @@ -141,4 +155,126 @@ public class RatingRestApiTest extends BaseWebScriptTest final JSONArray ratingsArray = dataObj.getJSONArray("ratings"); assertEquals(0, ratingsArray.length()); } + + public void testApplyRatingAndRetrieve() throws Exception + { + // POST a new rating to the testNode - as User One. + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + + final String ratingUrl = getRatingUrl(testNode); + + final int ratingValue = 5; + String jsonString = new JSONStringer().object() + .key("rating").value(ratingValue) + .key("ratingScheme").value("fiveStarRatingScheme") + .endObject() + .toString(); + + Response rsp = sendRequest(new PostRequest(ratingUrl, + jsonString, APPLICATION_JSON), 200); + + String rspContent = rsp.getContentAsString(); + + // Get the returned URL and validate + JSONObject jsonRsp = new JSONObject(new JSONTokener(rspContent)); + + JSONObject dataObj = (JSONObject)jsonRsp.get("data"); + assertNotNull("JSON 'data' object was null", dataObj); + String returnedUrl = dataObj.getString("ratedNodeUrl"); + assertEquals(ratingUrl, returnedUrl); + + // Now GET the ratings via that returned URL + rsp = sendRequest(new GetRequest(ratingUrl), 200); + + jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); + + dataObj = (JSONObject)jsonRsp.get("data"); + assertNotNull("JSON 'data' object was null", dataObj); + + // There should only be the one rating in there. + final JSONArray ratingsArray = dataObj.getJSONArray("ratings"); + assertEquals(1, ratingsArray.length()); + JSONObject firstRating = (JSONObject)ratingsArray.get(0); + assertEquals(ratingValue, firstRating.getInt("rating")); + assertEquals("fiveStarRatingScheme", firstRating.getString("ratingScheme")); + + + + // Now POST a second new rating to the testNode - as User Two. + AuthenticationUtil.setFullyAuthenticatedUser(USER_TWO); + + final int userTwoRatingValue = 3; + jsonString = new JSONStringer().object() + .key("rating").value(userTwoRatingValue) + .key("ratingScheme").value("fiveStarRatingScheme") + .endObject() + .toString(); + + rsp = sendRequest(new PostRequest(ratingUrl, + jsonString, APPLICATION_JSON), 200); + rspContent = rsp.getContentAsString(); + + // Get the returned URL and validate + jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); + + dataObj = (JSONObject)jsonRsp.get("data"); + assertNotNull("JSON 'data' object was null", dataObj); + returnedUrl = dataObj.getString("ratedNodeUrl"); + + // Again GET the ratings via that returned URL + rsp = sendRequest(new GetRequest(returnedUrl), 200); + + jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); + + dataObj = (JSONObject)jsonRsp.get("data"); + assertNotNull("JSON 'data' object was null", dataObj); + + // There should still only be the one rating in the results - because we're running + // as UserTwo and should not see UserOne's rating. + final JSONArray userTwoRatingsArray = dataObj.getJSONArray("ratings"); + assertEquals(1, userTwoRatingsArray.length()); + JSONObject secondRating = (JSONObject)userTwoRatingsArray.get(0); + assertEquals(userTwoRatingValue, secondRating.getInt("rating")); + assertEquals("fiveStarRatingScheme", secondRating.getString("ratingScheme")); + + //TODO Could probably put the GET average call here then. + } + + /** + * This method gives the 'ratings' URL for the specified NodeRef. + */ + private String getRatingUrl(NodeRef nodeRef) + { + String nodeUrl = nodeRef.toString().replace("://", "/"); + String ratingUrl = MessageFormat.format(NODE_RATINGS_URL_FORMAT, nodeUrl); + return ratingUrl; + } + + private void createUser(String userName) + { + if (! authenticationService.authenticationExists(userName)) + { + authenticationService.createAuthentication(userName, "PWD".toCharArray()); + } + + if (! personService.personExists(userName)) + { + PropertyMap ppOne = new PropertyMap(4); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); + ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); + ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); + ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + personService.createPerson(ppOne); + } + } + + private void deleteUser(String userName) + { + if (personService.personExists(userName)) + { + personService.deletePerson(userName); + } + } }