org.sakaiproject.rubrics.logic.impl.RubricsServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.rubrics.logic.impl.RubricsServiceImpl.java

Source

/**********************************************************************************
 *
 * Copyright (c) 2017 The Sakai Foundation
 *
 * Original developers:
 *
 *   Unicon
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.osedu.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **********************************************************************************/

package org.sakaiproject.rubrics.logic.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.time.format.DateTimeFormatter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;

import lombok.Getter;
import lombok.Setter;

import net.sf.ehcache.Cache;

import org.apache.log4j.Logger;

import org.sakaiproject.authz.api.FunctionManager;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.rubrics.model.Criterion;
import org.sakaiproject.rubrics.model.Evaluation;
import org.sakaiproject.rubrics.model.Rating;
import org.sakaiproject.rubrics.model.Rubric;
import org.sakaiproject.rubrics.model.ToolItemRubricAssociation;
import org.sakaiproject.rubrics.logic.api.RubricsService;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.util.ResourceLoader;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.client.Traverson;
import org.springframework.hateoas.mvc.TypeReferences;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;

/**
 * Implementation of {@link RubricsService}
 */
@Slf4j
public class RubricsServiceImpl implements RubricsService {

    protected static ResourceLoader rb = new ResourceLoader("rubricsMessages");

    private static final String RBCS_PERMISSIONS_EVALUATOR = "rbcs.evaluator";
    private static final String RBCS_PERMISSIONS_EDITOR = "rbcs.editor";
    private static final String RBCS_PERMISSIONS_EVALUEE = "rbcs.evaluee";
    private static final String RBCS_PERMISSIONS_ASSOCIATOR = "rbcs.associator";
    private static final String RBCS_PERMISSIONS_SUPERUSER = "rbcs.superuser";

    private static final String RBCS_SERVICE_URL_PREFIX = "/rubrics-service/rest/";

    private static final String RUBRICS_TOKEN_SIGNING_SHARED_SECRET_PROPERTY = "rubrics.integration.token-secret";
    private static final String SITE_CONTEXT_TYPE = "site";

    private static final String SERVER_ID_PROPERTY = "sakai.serverId";

    private static final String JWT_ISSUER = "sakai";
    private static final String JWT_AUDIENCE = "rubrics";
    private static final String JWT_CUSTOM_CLAIM_TOOL_ID = "toolId";
    private static final String JWT_CUSTOM_CLAIM_SESSION_ID = "sessionId";
    private static final String JWT_CUSTOM_CLAIM_ROLES = "roles";
    private static final String JWT_CUSTOM_CLAIM_CONTEXT_ID = "contextId";
    private static final String JWT_CUSTOM_CLAIM_CONTEXT_TYPE = "contextType";

    @Getter
    @Setter
    private ToolManager toolManager;

    @Getter
    @Setter
    private SessionManager sessionManager;

    @Getter
    @Setter
    private UserDirectoryService userDirectoryService;

    @Getter
    @Setter
    private SecurityService securityService;

    @Getter
    @Setter
    private EventTrackingService eventTrackingService;

    @Getter
    @Setter
    private ServerConfigurationService serverConfigurationService;

    @Getter
    @Setter
    private SiteService siteService;

    @Getter
    @Setter
    private FunctionManager functionManager;

    @Getter
    @Setter
    private AuthzGroupService authzGroupService;

    @Setter
    private Cache cache;

    public void init() {
        if (StringUtils
                .isBlank(serverConfigurationService.getString(RUBRICS_TOKEN_SIGNING_SHARED_SECRET_PROPERTY))) {
            throw new IllegalStateException(String.format(
                    "Required deployment property %s was not found. Please " + "configure it in sakai.properties.",
                    RUBRICS_TOKEN_SIGNING_SHARED_SECRET_PROPERTY));
        }

        setFunction(RBCS_PERMISSIONS_EVALUATOR);
        setFunction(RBCS_PERMISSIONS_EDITOR);
        setFunction(RBCS_PERMISSIONS_EVALUEE);
        setFunction(RBCS_PERMISSIONS_ASSOCIATOR);
    }

    /**
     * {@inheritDoc}
     */
    private void setFunction(String function) {
        functionManager.registerFunction(function);
    }

    @PostConstruct
    private void postConstruct() {

    }

    public String generateJsonWebToken(String tool) {

        String token = null;

        String userId = sessionManager.getCurrentSessionUserId();

        try {

            String siteId = toolManager.getCurrentPlacement().getContext();

            DateTime now = DateTime.now();

            JWTCreator.Builder jwtBuilder = JWT.create();
            jwtBuilder.withIssuer(JWT_ISSUER).withAudience(JWT_AUDIENCE).withSubject(userId)
                    .withClaim(JWT_CUSTOM_CLAIM_TOOL_ID, tool)
                    .withClaim(JWT_CUSTOM_CLAIM_SESSION_ID, sessionManager.getCurrentSession().getId())
                    .withIssuedAt(now.toDate());
            int sessionTimeoutInSeconds = sessionManager.getCurrentSession().getMaxInactiveInterval();
            if (sessionTimeoutInSeconds > 0) {
                jwtBuilder.withExpiresAt(now.plusSeconds(sessionTimeoutInSeconds).toDate());
            } else {
                // if Sakai is configured for sessions to never timeout (negative value), we will set 30 minutes for
                // tokens - the rubrics service will check Sakai session validity if it receives an expired token.
                jwtBuilder.withExpiresAt(now.plusMinutes(30).toDate());
            }

            if (securityService.isSuperUser()) {
                jwtBuilder.withArrayClaim(JWT_CUSTOM_CLAIM_ROLES,
                        new String[] { RBCS_PERMISSIONS_EDITOR, RBCS_PERMISSIONS_ASSOCIATOR,
                                RBCS_PERMISSIONS_EVALUATOR, RBCS_PERMISSIONS_EVALUEE, RBCS_PERMISSIONS_SUPERUSER });

            } else {

                List<String> roles = new ArrayList<>();
                if (authzGroupService.isAllowed(userId, RBCS_PERMISSIONS_EDITOR, "/site/" + siteId)) {
                    roles.add(RBCS_PERMISSIONS_EDITOR);
                }
                if (authzGroupService.isAllowed(userId, RBCS_PERMISSIONS_ASSOCIATOR, "/site/" + siteId)) {
                    roles.add(RBCS_PERMISSIONS_ASSOCIATOR);
                }
                if (authzGroupService.isAllowed(userId, RBCS_PERMISSIONS_EVALUATOR, "/site/" + siteId)) {
                    roles.add(RBCS_PERMISSIONS_EVALUATOR);
                }
                if (authzGroupService.isAllowed(userId, RBCS_PERMISSIONS_EVALUEE, "/site/" + siteId)) {
                    roles.add(RBCS_PERMISSIONS_EVALUEE);
                }
                jwtBuilder.withArrayClaim(JWT_CUSTOM_CLAIM_ROLES, roles.toArray(new String[] {}));
            }
            jwtBuilder.withClaim(JWT_CUSTOM_CLAIM_CONTEXT_ID, siteId);
            jwtBuilder.withClaim(JWT_CUSTOM_CLAIM_CONTEXT_TYPE, SITE_CONTEXT_TYPE);
            token = jwtBuilder.sign(Algorithm
                    .HMAC256(serverConfigurationService.getString(RUBRICS_TOKEN_SIGNING_SHARED_SECRET_PROPERTY)));

        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(String.format(
                    "An error occurred while generating a JSON Web Token to "
                            + "authorize communication with the Rubrics service. Please verify the %s property is "
                            + "defined in the sakai.properties file.",
                    RUBRICS_TOKEN_SIGNING_SHARED_SECRET_PROPERTY), e);
        }

        return token;
    }

    public boolean hasAssociatedRubric(String tool, String id) {

        boolean exists = false;

        try {

            Optional<ToolItemRubricAssociation> association = getRubricAssociation(tool, id);
            exists = association.isPresent();

        } catch (Exception e) {
            log.debug("No previous association or rubrics not answering", e);
        }

        return exists;
    }

    /**
     * call the rubrics-service to save the binding between assignment and rubric
     * @param params A hashmap with all the rbcs params comming from the component. The tool should generate it.
     * @param tool the tool id, something like "sakai.assignment"
     * @param id the id of the element to
     */

    public void saveRubricAssociation(String tool, String id, HashMap<String, String> params) {

        String associationHref = null;
        String created = "";
        String owner = "";
        Map<String, Boolean> oldParams = new HashMap<>();

        try {
            Optional<Resource<ToolItemRubricAssociation>> associationResource = getRubricAssociationResource(tool,
                    id);
            if (associationResource.isPresent()) {
                associationHref = associationResource.get().getLink(Link.REL_SELF).getHref();
                ToolItemRubricAssociation association = associationResource.get().getContent();
                created = association.getMetadata().getCreated().toString();
                owner = association.getMetadata().getOwner();
                oldParams = association.getParameters();
            }

            //we will create a new one or update if the parameter rbcs-associate is true
            String nowTime = DateTimeFormatter.ISO_INSTANT.format(Instant.now());
            if (params.get("rbcs-associate").equals("1")) {

                if (associationHref == null) { // create a new one.
                    String input = "{\"toolId\" : \"" + tool + "\",\"itemId\" : \"" + id + "\",\"rubricId\" : "
                            + params.get("rbcs-rubricslist") + ",\"metadata\" : {\"created\" : \"" + nowTime
                            + "\",\"modified\" : \"" + nowTime + "\",\"owner\" : \""
                            + userDirectoryService.getCurrentUser().getId() + "\"},\"parameters\" : {"
                            + setConfigurationParameters(params, oldParams) + "}}";
                    log.debug("New association " + input);
                    String query = serverConfigurationService.getServerUrl() + RBCS_SERVICE_URL_PREFIX
                            + "rubric-associations/";
                    String resultPost = postRubricResource(query, input, tool);
                    log.debug("resultPost: " + resultPost);
                } else {
                    String input = "{\"toolId\" : \"" + tool + "\",\"itemId\" : \"" + id + "\",\"rubricId\" : "
                            + params.get("rbcs-rubricslist") + ",\"metadata\" : {\"created\" : \"" + created
                            + "\",\"modified\" : \"" + nowTime + "\",\"owner\" : \"" + owner
                            + "\"},\"parameters\" : {" + setConfigurationParameters(params, oldParams) + "}}";
                    log.debug("Existing association update" + input);
                    String resultPut = putRubricResource(associationHref, input, tool);
                    //update the actual one.
                    log.debug("resultPUT: " + resultPut);
                }
            } else {
                // We delete the association
                if (associationHref != null) {
                    deleteRubricAssociation(associationHref, tool);
                }
            }

        } catch (Exception e) {
            //TODO If we have an error here, maybe we should return say something to the user
        }
    }

    public void saveRubricEvaluation(String toolId, String associatedItemId, String evaluatedItemId,
            String evaluatedItemOwnerId, String evaluatorId, HashMap<String, String> params) {

        String evaluationUri = null;
        String created = "";
        String owner = "";

        try {

            // Check for an existing evaluation
            Evaluation existingEvaluation = null;
            String rubricEvaluationId = null;

            try {

                TypeReferences.ResourcesType<Resource<Evaluation>> resourceParameterizedTypeReference = new TypeReferences.ResourcesType<Resource<Evaluation>>() {
                };

                URI apiBaseUrl = new URI(serverConfigurationService.getServerUrl() + RBCS_SERVICE_URL_PREFIX);
                Traverson traverson = new Traverson(apiBaseUrl, MediaTypes.HAL_JSON);

                Traverson.TraversalBuilder builder = traverson.follow("evaluations", "search",
                        "by-tool-item-and-associated-item-and-evaluated-item-ids");

                HttpHeaders headers = new HttpHeaders();
                headers.add("Authorization", String.format("Bearer %s", generateJsonWebToken(toolId)));
                builder.withHeaders(headers);

                Map<String, Object> parameters = new HashMap<>();
                parameters.put("toolId", toolId);
                parameters.put("itemId", associatedItemId);
                parameters.put("evaluatedItemId", evaluatedItemId);
                parameters.put("evaluatorId", evaluatorId);

                Resources<Resource<Evaluation>> evaluationResources = builder.withTemplateParameters(parameters)
                        .toObject(resourceParameterizedTypeReference);

                // Should only be one matching this search criterion
                if (evaluationResources.getContent().size() > 1) {
                    throw new IllegalStateException(
                            String.format("Number of evaluation resources greater than one for request: %s",
                                    evaluationResources.getLink(Link.REL_SELF).toString()));
                }
                for (Resource<Evaluation> evaluationResource : evaluationResources) {
                    existingEvaluation = evaluationResource.getContent();
                    evaluationUri = evaluationResource.getLink(Link.REL_SELF).getHref();
                }

            } catch (Exception ex) {
                log.info(ex.getMessage());
                //no previous evaluation
            }

            // Get the actual association (necessary to get the rubrics association resource for persisting
            // the evaluation)
            Resource<ToolItemRubricAssociation> rubricToolItemAssociationResource = getRubricAssociationResource(
                    toolId, associatedItemId).get();

            String criterionJsonData = createCriterionJsonPayload(params, rubricToolItemAssociationResource);

            if (existingEvaluation == null) { // Create a new one

                String input = String.format("{ \"evaluatorId\" : \"%s\",\"evaluatedItemId\" : \"%s\", "
                        + "\"evaluatedItemOwnerId\" : \"%s\"," + "\"overallComment\" : \"%s\", "
                        + "\"toolItemRubricAssociation\" : \"%s\", " + "\"criterionOutcomes\" : [ %s ] " + "}",
                        evaluatorId, evaluatedItemId, evaluatedItemOwnerId, "",
                        rubricToolItemAssociationResource.getLink(Link.REL_SELF).getHref(), criterionJsonData);

                String requestUri = serverConfigurationService.getServerUrl() + RBCS_SERVICE_URL_PREFIX
                        + "evaluations/";
                String resultPost = postRubricResource(requestUri, input, toolId);
                log.debug("resultPost: " + resultPost);

            } else { // Update existing evaluation

                // Resource IDs return as null when using Spring HATEOAS due to https://github.com/spring-projects/spring-hateoas/issues/67
                // so ID is not added and the resource URI is where it is derived from.

                String input = String.format("{ \"evaluatorId\" : \"%s\",\"evaluatedItemId\" : \"%s\", "
                        + "\"evaluatedItemOwnerId\" : \"%s\", \"overallComment\" : \"%s\", \"toolItemRubricAssociation\" : \"%s\", "
                        + "\"criterionOutcomes\" : [ %s ] }", evaluatorId, evaluatedItemId, evaluatedItemOwnerId,
                        "", rubricToolItemAssociationResource.getLink(Link.REL_SELF).getHref(), criterionJsonData);

                String resultPut = putRubricResource(evaluationUri, input, toolId);
                //lets update the actual one.
                log.debug("resultPUT: " + resultPut);
            }

        } catch (Exception e) {
            //TODO If we have an error here, maybe we should return say something to the user
            log.error("Error in SaveRubricEvaluation" + e.getMessage());
        }

    }

    private String createCriterionJsonPayload(HashMap<String, String> formPostParameters,
            Resource<ToolItemRubricAssociation> association) throws Exception {

        Map<String, Map<String, String>> criterionDataMap = extractCriterionDataFromParams(formPostParameters);

        String criterionJsonData = "";
        int index = 0;
        boolean pointsAdjusted = false;
        String points = null;
        String selectedRatingId = null;

        String inlineRubricUri = String.format("%s?%s", association.getLink("rubric").getHref(),
                "projection=inlineRubric");

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.add("Authorization",
                String.format("Bearer %s", generateJsonWebToken(association.getContent().getToolId())));
        HttpEntity<?> requestEntity = new HttpEntity<>(headers);
        ResponseEntity<Rubric> rubricEntity = restTemplate.exchange(inlineRubricUri, HttpMethod.GET, requestEntity,
                Rubric.class);

        Map<String, Criterion> criterions = new HashMap<>();
        for (Criterion criterion : rubricEntity.getBody().getCriterions()) {
            criterions.put(String.valueOf(criterion.getId()), criterion);
        }

        for (Map.Entry<String, Map<String, String>> criterionData : criterionDataMap.entrySet()) {
            if (index > 0) {
                criterionJsonData += ", ";
            }
            index++;

            final String selectedRatingPoints = criterionData.getValue().get("rbcs-criterion");

            if (StringUtils.isNotBlank(criterionData.getValue().get("rbcs-criterion-override"))) {
                pointsAdjusted = true;
                points = criterionData.getValue().get("rbcs-criterion-override");
            } else {
                pointsAdjusted = false;
                points = selectedRatingPoints;
            }

            Criterion criterion = criterions.get(criterionData.getKey());
            Optional<Rating> rating = criterion.getRatings().stream()
                    .filter(c -> String.valueOf(c.getPoints()).equals(selectedRatingPoints)).findFirst();

            if (rating.isPresent()) {
                selectedRatingId = String.valueOf(rating.get().getId());
            }

            if (StringUtils.isEmpty(points)) {
                points = "0";
            }

            criterionJsonData += String.format(
                    "{ \"criterionId\" : \"%s\", \"points\" : \"%s\", "
                            + "\"comments\" : \"%s\", \"pointsAdjusted\" : %b, \"selectedRatingId\" : \"%s\"  }",
                    criterionData.getKey(), points,
                    StringEscapeUtils.escapeJson(criterionData.getValue().get("rbcs-criterion-comment")),
                    pointsAdjusted, selectedRatingId);
        }

        return criterionJsonData;
    }

    private Map<String, Map<String, String>> extractCriterionDataFromParams(HashMap<String, String> params) {

        Map<String, Map<String, String>> criterionDataMap = new HashMap();

        for (Map.Entry<String, String> param : params.entrySet()) {
            String possibleCriterionId = StringUtils.substringAfterLast(param.getKey(), "-");
            String criterionDataLabel = StringUtils.substringBeforeLast(param.getKey(), "-");
            if (StringUtils.isNumeric(possibleCriterionId)) {
                if (!criterionDataMap.containsKey(possibleCriterionId)) {
                    criterionDataMap.put(possibleCriterionId, new HashMap());
                }
                criterionDataMap.get(possibleCriterionId).put(criterionDataLabel, param.getValue());
            }
        }

        return criterionDataMap;
    }

    /**
     * Prepare the association params in json format
     * @param params the full list of rubrics params coming from the component
     * @return
     */

    private String setConfigurationParameters(HashMap<String, String> params, Map<String, Boolean> oldParams) {
        String configuration = "";
        Boolean noFirst = false;
        //Get the parameters
        Iterator it2 = params.keySet().iterator();
        while (it2.hasNext()) {
            String name = it2.next().toString();
            if (name.startsWith("rbcs-config-")) {
                if (noFirst) {
                    configuration = configuration + " , ";
                }
                String value = "false";
                if ((params.get(name) != null) && (params.get(name).equals("1"))) {
                    value = "true";
                }
                configuration = configuration + "\"" + name.substring(12) + "\" : " + value;
                noFirst = true;
            }
        }
        Iterator itOld = oldParams.keySet().iterator();
        while (itOld.hasNext()) {
            String name = itOld.next().toString();
            if (!(params.containsKey("rbcs-config-" + name))) {
                if (noFirst) {
                    configuration = configuration + " , ";
                }
                configuration = configuration + "\"" + name + "\" : false";
                noFirst = true;
            }
        }
        log.debug(configuration);
        return configuration;
    }

    /**
     * Returns the ToolItemRubricAssociation for the given tool and associated item ID, wrapped as an Optional.
     * @param toolId the tool id, something like "sakai.assignment"
     * @param associatedToolItemId the id of the associated element within the tool
     * @return
     */
    public Optional<ToolItemRubricAssociation> getRubricAssociation(String toolId, String associatedToolItemId)
            throws Exception {

        Optional<ToolItemRubricAssociation> association = Optional.empty();

        Optional<Resource<ToolItemRubricAssociation>> associationResource = getRubricAssociationResource(toolId,
                associatedToolItemId);
        if (associationResource.isPresent()) {
            association = Optional.of(associationResource.get().getContent());
        }
        return association;
    }

    /**
     * Returns the ToolItemRubricAssociation resource for the given tool and associated item ID, wrapped as an Optional.
     * @param toolId the tool id, something like "sakai.assignment"
     * @param associatedToolItemId the id of the associated element within the tool
     * @return
     */
    protected Optional<Resource<ToolItemRubricAssociation>> getRubricAssociationResource(String toolId,
            String associatedToolItemId) throws Exception {

        TypeReferences.ResourcesType<Resource<ToolItemRubricAssociation>> resourceParameterizedTypeReference = new TypeReferences.ResourcesType<Resource<ToolItemRubricAssociation>>() {
        };

        URI apiBaseUrl = new URI(serverConfigurationService.getServerUrl() + RBCS_SERVICE_URL_PREFIX);
        Traverson traverson = new Traverson(apiBaseUrl, MediaTypes.HAL_JSON);

        Traverson.TraversalBuilder builder = traverson.follow("rubric-associations", "search", "by-tool-item-ids");

        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", String.format("Bearer %s", generateJsonWebToken(toolId)));
        builder.withHeaders(headers);

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("toolId", toolId);
        parameters.put("itemId", associatedToolItemId);

        Resources<Resource<ToolItemRubricAssociation>> associationResources = builder
                .withTemplateParameters(parameters).toObject(resourceParameterizedTypeReference);

        // Should only be one matching this search criterion
        if (associationResources.getContent().size() > 1) {
            throw new IllegalStateException(
                    String.format("Number of rubric association resources greater than one for request: %s",
                            associationResources.getLink(Link.REL_SELF).toString()));
        }

        Optional<Resource<ToolItemRubricAssociation>> associationResource = associationResources.getContent()
                .stream().findFirst();

        return associationResource;
    }

    //TODO generate a public String postRubricAssociation(String tool, String id, HashMap<String,String> params)

    /**
     * Posts the rubric association.
     * @param json The json to post.
     * @return
     */
    private String postRubricResource(String targetUri, String json, String toolId) throws IOException {
        log.debug(String.format("Post to URI '%s' body:", targetUri, json));

        HttpURLConnection conn = null;
        try {
            URL url = new URL(targetUri);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setRequestProperty("Content-Type", "application/hal+json; charset=UTF-8");
            conn.setRequestProperty("Content-Length", "" + json.length());
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            String cookie = "JSESSIONID=" + sessionManager.getCurrentSession().getId() + ""
                    + System.getProperty(SERVER_ID_PROPERTY);
            conn.setRequestProperty("Cookie", cookie);
            conn.setRequestProperty("Authorization", "Bearer " + generateJsonWebToken(toolId));

            try (OutputStream os = conn.getOutputStream()) {
                os.write(json.getBytes("UTF-8"));
                os.close();
            }

            // read the response
            BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

            String output;
            StringWriter result = new StringWriter();
            while ((output = br.readLine()) != null) {
                result.append(output + "\n");
            }

            return result.toString();

        } catch (IOException ioException) {

            log.warn(String.format("Error creating a rubric resource at %s", targetUri), ioException);
            return null;

        } finally {
            if (conn != null) {
                try {
                    conn.disconnect();
                } catch (Exception e) {

                }
            }
        }
    }

    //TODO generate a public String putRubricAssociation(String tool, String id, HashMap<String,String> params)

    /**
     * Put the rubric association.
     * @param targetUri The association href.
     * @param json The json to post.
     * @return
     */
    private String putRubricResource(String targetUri, String json, String toolId) throws IOException {
        log.debug(String.format("PUT to URI '%s' body:", targetUri, json));

        HttpURLConnection conn = null;
        try {
            URL url = new URL(targetUri);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            conn.setRequestProperty("Content-Length", "" + json.length());
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("PUT");
            String cookie = "JSESSIONID=" + sessionManager.getCurrentSession().getId() + ""
                    + System.getProperty(SERVER_ID_PROPERTY);
            conn.setRequestProperty("Cookie", cookie);
            conn.setRequestProperty("Authorization", "Bearer " + generateJsonWebToken(toolId));

            try (OutputStream os = conn.getOutputStream()) {
                os.write(json.getBytes("UTF-8"));
                os.close();
            }

            // read the response
            BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

            String output;
            StringWriter result = new StringWriter();
            while ((output = br.readLine()) != null) {
                result.append(output + "\n");
            }

            return result.toString();

        } catch (IOException ioException) {

            log.warn(String.format("Error updating a rubric resource at %s", targetUri), ioException);
            return null;
        } finally {
            if (conn != null) {
                try {
                    conn.disconnect();
                } catch (Exception e) {

                }
            }
        }
    }

    //TODO generate a public String deleteRubricAssociation(String tool, String id)

    /**
     * Delete the rubric association.
     * @param query The association href.
     * @return
     */

    private void deleteRubricAssociation(String query, String toolId) throws IOException {

        HttpURLConnection conn = null;
        try {
            URL url = new URL(query);
            String cookie = "JSESSIONID=" + sessionManager.getCurrentSession().getId() + ""
                    + System.getProperty(SERVER_ID_PROPERTY);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("DELETE");
            conn.setRequestProperty("Accept", "application/json");
            conn.setRequestProperty("Cookie", cookie);
            conn.setRequestProperty("Authorization", "Bearer " + generateJsonWebToken(toolId));

            if (conn.getResponseCode() != 204) {
                throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode());
            }

        } catch (MalformedURLException e) {

            log.warn("Error deleting a rubric association " + e.getMessage());

        } catch (IOException e) {

            log.warn("Error deleting a rubric association" + e.getMessage());

        } finally {
            if (conn != null) {
                try {
                    conn.disconnect();
                } catch (Exception e) {

                }
            }
        }
    }

    /**
     * Returns the JSON string for the rubric evaluation
     * @param toolId the tool id, something like "sakai.assignment"
     * @param associatedToolItemId the id of the tool item which has a rubric associated to it (e.g. assignment ID)
     * @param evaluatedItemId  the id of the tool item which is being evaluated using a rubric (e.g. assignment submission ID)
     * @return
     */
    public String getRubricEvaluation(String toolId, String associatedToolItemId, String evaluatedItemId)
            throws IOException {

        HttpURLConnection conn = null;
        try {
            URL url = new URL(serverConfigurationService.getServerUrl() + RBCS_SERVICE_URL_PREFIX
                    + "evaluations/search/by-tool-item-and-associated-item-and-evaluated-item-ids?toolId=" + toolId
                    + "&itemId=" + associatedToolItemId + "&evaluatedItemId=" + evaluatedItemId);

            String cookie = "JSESSIONID=" + sessionManager.getCurrentSession().getId() + ""
                    + System.getProperty(SERVER_ID_PROPERTY);

            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept", "application/json");
            conn.setRequestProperty("Cookie", cookie);
            conn.setRequestProperty("Authorization", "Bearer" + generateJsonWebToken(toolId));

            if (conn.getResponseCode() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode());
            }

            BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

            String output;
            StringWriter result = new StringWriter();
            while ((output = br.readLine()) != null) {
                result.append(output + "\n");
            }

            return result.toString();

        } catch (MalformedURLException e) {

            log.warn("Error getting a rubric evaluation " + e.getMessage());
            return null;

        } catch (IOException e) {

            log.warn("Error getting a rubric evaluation" + e.getMessage());
            return null;
        } finally {
            if (conn != null) {
                try {
                    conn.disconnect();
                } catch (Exception e) {

                }
            }
        }
    }

    public String generateLang() {

        StringBuilder lines = new StringBuilder();
        lines.append("var rubricsLang = {");

        Locale locale = rb.getLocale();
        Set properties = rb.keySet();
        lines.append("'" + locale.toLanguageTag() + "': {");
        Iterator keys = properties.iterator();
        while (keys.hasNext()) {
            String key = keys.next().toString();
            if (keys.hasNext()) {
                lines.append("'" + key + "': '" + rb.getString(key) + "',");
            } else {
                lines.append("'" + key + "': '" + rb.getString(key) + "'");
            }
        }

        lines.append("}");
        lines.append("}");

        log.debug(lines.toString());

        return lines.toString();
    }

}