org.opentestsystem.authoring.testspecbank.client.TestSpecBankClient.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.authoring.testspecbank.client.TestSpecBankClient.java

Source

/*******************************************************************************
 * Educational Online Test Delivery System
 * Copyright (c) 2013 American Institutes for Research
 *
 * Distributed under the AIR Open Source License, Version 1.0
 * See accompanying file AIR-License-1_0.txt or at
 * http://www.smarterapp.org/documents/American_Institutes_for_Research_Open_Source_Software_License.pdf
 ******************************************************************************/
package org.opentestsystem.authoring.testspecbank.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.opentestsystem.authoring.testspecbank.client.domain.TestSpecBankClientObj;
import org.opentestsystem.authoring.testspecbank.client.domain.TestSpecificationSearchResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.Resource;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import tds.common.ValidationError;
import tds.common.web.resources.NoContentResponseResource;

/**
 * Integrated implementation of the Test Specification Bank client. Talks to a running instance of the Test Specification Bank REST api.
 */
@Component
public class TestSpecBankClient implements TestSpecBankClientInterface {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestSpecBankClient.class);

    /**
     * RestTemplate used to talk to the REST API
     */
    @Resource
    @Qualifier("tbsRestTemplate")
    protected RestOperations tsbRestTemplate;

    /**
     * RestTemplate used to talk to a REST API using OAuth authentication.
     * <p>
     * This rest template is used because it does not rely on the
     * {@link org.opentestsystem.shared.security.oauth.client.grant.samlbearer.SamlAssertionAccessTokenProvider}.
     * Using the tsbRestTemplate declared above results in the following error when called from ART:
     *     <pre>
     *        java.lang.String cannot be cast to org.springframework.security.saml.SAMLCredential
     *     </pre>
     * </p>
     */
    @Resource
    @Qualifier("oauthRestTemplate")
    protected OAuth2RestTemplate tsbOauthRestTemplate;
    /**
     * Base URI to use to talk to REST API
     */
    @Value("${tsb.tsbUrl:}")
    private String baseUri;

    /**
     * {@link com.fasterxml.jackson.databind.ObjectMapper} configured with the {@link com.fasterxml.jackson.datatype.guava.GuavaModule}
     */
    @Autowired
    @Qualifier("integrationObjectMapper")
    private ObjectMapper objectMapper;

    // tenant is separate as a reminder that tenant is very much required, the contents of the parameter map are optional and not enforced
    @Override
    public TestSpecificationSearchResponse getTestSpecificationByTenantSet(final Set<String> tenantIds,
            Map<String, String[]> parameterMap) {
        TestSpecificationSearchResponse response;
        if (parameterMap == null) {
            parameterMap = Maps.newHashMap();
        }

        final String[] tenants = tenantIds.toArray(new String[0]);
        if (ArrayUtils.isNotEmpty(tenants)) {
            parameterMap.put("tenantSet", tenants);
        }
        try {
            return this.tsbRestTemplate.getForObject(this.baseUri + buildTestSpecURL(parameterMap),
                    TestSpecificationSearchResponse.class, convertStringArray(parameterMap));
        } catch (final RestClientException e) {
            response = new TestSpecificationSearchResponse();
            LOGGER.error("Error searching for test specifications", e);
        }
        return response;
    }

    @Override
    public Optional<ValidationError> deleteTestSpecification(final String testSpecificationKey) {
        Optional<ValidationError> maybeValidationError = Optional.empty();
        final URI uri = UriComponentsBuilder
                .fromUriString(String.format("%stestSpecification/%s", baseUri, testSpecificationKey)).build()
                .toUri();

        try {
            tsbOauthRestTemplate.delete(uri);
        } catch (final HttpClientErrorException hce) {
            LOGGER.error("Error deleting {0} test specification: ", hce);
            if (hce.getStatusCode() == HttpStatus.UNPROCESSABLE_ENTITY) {
                try {
                    // The NoContentResponseResource contains an array of ValidationErrors.  If we got to this point,
                    // the TestSpecificationController#deleteTestSpecification endpoint will have returned a
                    // NoContentResponseResource with a single ValidationError describing what went wrong.
                    final NoContentResponseResource response = objectMapper.readValue(hce.getResponseBodyAsString(),
                            NoContentResponseResource.class);
                    maybeValidationError = Optional.of(response.getErrors()[0]);
                } catch (final Exception mapEx) {
                    LOGGER.error(String.format("Error mapping response %s to ValidationError: ",
                            hce.getResponseBodyAsString()), mapEx);
                    final String errorMessage = mapEx.getMessage() == null ? mapEx.getClass().getName()
                            : mapEx.getMessage();
                    maybeValidationError = Optional.of(new ValidationError("mapping exception", errorMessage));
                }
            } else {
                maybeValidationError = Optional.of(new ValidationError("client exception", hce.getMessage()));
            }
        } catch (final Exception e) {
            LOGGER.error("Error deleting {0} test specification: ", e);
            maybeValidationError = Optional.of(new ValidationError("server exception", e.getMessage()));
        }

        return maybeValidationError;
    }

    private Map<String, String> convertStringArray(final Map<String, String[]> inParameterMap) {
        final Map<String, String> ret = new HashMap<String, String>();
        if (inParameterMap != null && inParameterMap.size() > 0) {
            for (final Map.Entry<String, String[]> entry : inParameterMap.entrySet()) {
                if (entry != null && entry.getValue() != null && entry.getValue().length > 0) {
                    String retValue = "";
                    if (entry.getValue().length == 1) {
                        retValue = entry.getValue()[0];
                    } else {
                        retValue = StringUtils.join(entry.getValue(), ",");
                    }
                    ret.put(entry.getKey(), retValue);
                }
            }
        }
        return ret;
    }

    private String buildTestSpecURL(final Map<String, String[]> inParameterMap) {
        String ret = "testSpecification";
        if (inParameterMap != null && inParameterMap.size() > 0) {
            ret += "?";
            for (final String key : inParameterMap.keySet()) {
                ret += key + "={" + key + "}&";
            }
        }
        return ret;
    }

    @Override
    public TestSpecBankClientObj publishTestSpecification(final TestSpecBankClientObj testSpecification) {
        final ResponseEntity<TestSpecBankClientObj> response = this.tsbOauthRestTemplate
                .postForEntity(this.baseUri + "testSpecification", testSpecification, TestSpecBankClientObj.class);
        return response.getBody();
    }
}