org.openmhealth.shim.healthvault.HealthvaultShim.java Source code

Java tutorial

Introduction

Here is the source code for org.openmhealth.shim.healthvault.HealthvaultShim.java

Source

/*
 * Copyright 2014 Open mHealth
 *
 * Licensed under the Apache 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.apache.org/licenses/LICENSE-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.openmhealth.shim.healthvault;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.jayway.jsonpath.JsonPath;
import com.microsoft.hsg.*;
import net.minidev.json.JSONObject;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.openmhealth.schema.pojos.build.*;
import org.openmhealth.schema.pojos.*;
import org.openmhealth.schema.pojos.generic.MassUnitValue;
import org.openmhealth.shim.*;
import org.springframework.util.CollectionUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.util.*;

import static org.openmhealth.schema.pojos.generic.DurationUnitValue.DurationUnit;
import static org.openmhealth.schema.pojos.generic.LengthUnitValue.LengthUnit;

/**
 * Notes the healthvault shim is neither OAuth1.0/2.0, it's
 * a custom implementation.
 *
 * @author Danilo Bonilla
 */
public class HealthvaultShim implements Shim {

    public static final String SHIM_KEY = "healthvault";

    private static final String AUTHORIZE_URL = "https://account.healthvault-ppe.com";

    private static final String ACTION_QS = "/";

    public static final String TOKEN_PARAM = "wctoken";

    public static final String RECORD_ID_PARAM = "recordId";

    private Connection connection = null; //ConnectionFactory.getConnection();

    private static DateTimeFormatter formatterMins = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
    private static DateTimeFormatter formatterDate = DateTimeFormat.forPattern("yyyy-MM-dd");

    private AuthorizationRequestParametersRepo authorizationRequestParametersRepo;

    private ShimServerConfig shimServerConfig;

    private HealthvaultConfig config;

    public HealthvaultShim(AuthorizationRequestParametersRepo authorizationRequestParametersRepo,
            ShimServerConfig shimServerConfig, HealthvaultConfig healthvaultConfig) {
        this.authorizationRequestParametersRepo = authorizationRequestParametersRepo;
        this.shimServerConfig = shimServerConfig;
        this.config = healthvaultConfig;
        if (config != null && config.getClientId() != null) {
            this.connection = ConnectionFactory.getConnection(config.getClientId());
        }
    }

    @Override
    public String getLabel() {
        return "Microsoft Healthvault";
    }

    @Override
    public String getShimKey() {
        return SHIM_KEY;
    }

    @Override
    public String getClientId() {
        return config.getClientId();
    }

    @Override
    public String getBaseAuthorizeUrl() {
        return AUTHORIZE_URL;
    }

    @Override
    public ShimDataType[] getShimDataTypes() {
        return HealthVaultDataType.values();
    }

    public enum HealthVaultDataType implements ShimDataType {

        ACTIVITY("85a21ddb-db20-4c65-8d30-33c899ccf612", new JsonDeserializer<ShimDataResponse>() {
            @Override
            public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                    throws IOException {
                JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser);
                String rawJson = responseNode.toString();

                List<Activity> activities = new ArrayList<>();
                List<StepCount> stepCountList = new ArrayList<>();

                JsonPath activityPath = JsonPath.compile("$.things[*].data-xml.aerobic-session");

                final List<Object> hvActivities = JsonPath.read(rawJson, activityPath.getPath());
                if (CollectionUtils.isEmpty(hvActivities)) {
                    return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, null);
                }

                ObjectMapper mapper = new ObjectMapper();
                for (Object fva : hvActivities) {
                    final JsonNode hvActivity = mapper.readTree(((JSONObject) fva).toJSONString());

                    DateTime startTime = parseDateTimeFromWhenNode(hvActivity.get("when"));

                    JsonNode sessionNode = hvActivity.get("session");

                    Activity activity = new ActivityBuilder()
                            .setActivityName(sessionNode.get("mode").get("text").asText())
                            .setDistance(sessionNode.get("distance").get("display").get("").asDouble(),
                                    LengthUnit.m)
                            .withStartAndDuration(startTime, sessionNode.get("minutes").asDouble(),
                                    DurationUnit.min)
                            .build();

                    StepCount stepCount = new StepCountBuilder()
                            .setSteps(sessionNode.get("number-of-steps").asInt()).build();
                    stepCount.setEffectiveTimeFrame(activity.getEffectiveTimeFrame());

                    activities.add(activity);
                    stepCountList.add(stepCount);
                }
                Map<String, Object> results = new HashMap<>();
                results.put(Activity.SCHEMA_ACTIVITY, activities);
                results.put(StepCount.SCHEMA_STEP_COUNT, stepCountList);
                return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, results);
            }
        }),

        BLOOD_PRESSURE("ca3c57f4-f4c1-4e15-be67-0a3caf5414ed", new JsonDeserializer<ShimDataResponse>() {
            @Override
            public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                    throws IOException {

                JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser);
                String rawJson = responseNode.toString();

                List<BloodPressure> bloodPressures = new ArrayList<>();
                List<HeartRate> heartRates = new ArrayList<>();
                JsonPath bloodPressurePath = JsonPath.compile("$.things[*].data-xml.blood-pressure");

                List<Object> hvBloodPressures = JsonPath.read(rawJson, bloodPressurePath.getPath());
                if (CollectionUtils.isEmpty(hvBloodPressures)) {
                    return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, null);
                }
                ObjectMapper mapper = new ObjectMapper();
                for (Object fva : hvBloodPressures) {
                    JsonNode hvBloodPressure = mapper.readTree(((JSONObject) fva).toJSONString());

                    DateTime dateTimeWhen = parseDateTimeFromWhenNode(hvBloodPressure.get("when"));

                    bloodPressures.add(new BloodPressureBuilder().setTimeTaken(dateTimeWhen)
                            .setValues(new BigDecimal(hvBloodPressure.get("systolic").asText()),
                                    new BigDecimal(hvBloodPressure.get("diastolic").asText()))
                            .build());

                    heartRates.add(new HeartRateBuilder().withRate(hvBloodPressure.get("pulse").asInt())
                            .withTimeTaken(dateTimeWhen).build());
                }
                Map<String, Object> results = new HashMap<>();
                results.put(BloodPressure.SCHEMA_BLOOD_PRESSURE, bloodPressures);
                results.put(HeartRate.SCHEMA_HEART_RATE, heartRates);
                return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, results);
            }
        }),

        HEIGHT("40750a6a-89b2-455c-bd8d-b420a4cb500b", new JsonDeserializer<ShimDataResponse>() {
            @Override
            public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                    throws IOException, JsonProcessingException {
                JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser);
                String rawJson = responseNode.toString();

                List<BodyHeight> bodyHeights = new ArrayList<>();
                JsonPath bodyHeightsPath = JsonPath.compile("$.things[*].data-xml.height");

                List<Object> hvHeights = JsonPath.read(rawJson, bodyHeightsPath.getPath());
                if (CollectionUtils.isEmpty(hvHeights)) {
                    return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, null);
                }
                ObjectMapper mapper = new ObjectMapper();
                for (Object fva : hvHeights) {
                    JsonNode hvHeight = mapper.readTree(((JSONObject) fva).toJSONString());

                    DateTime dateTimeWhen = parseDateTimeFromWhenNode(hvHeight.get("when"));

                    BodyHeight bodyHeight = new BodyHeightBuilder()
                            .setHeight(hvHeight.get("value").get("display").get("").asText(),
                                    LengthUnit.in.toString())
                            .setTimeTaken(dateTimeWhen).build();

                    bodyHeights.add(bodyHeight);
                }
                Map<String, Object> results = new HashMap<>();
                results.put(BodyHeight.SCHEMA_BODY_HEIGHT, bodyHeights);
                return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, results);
            }
        }),

        BLOOD_GLUCOSE("879e7c04-4e8a-4707-9ad3-b054df467ce4", new JsonDeserializer<ShimDataResponse>() {
            @Override
            public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                    throws IOException {
                JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser);
                String rawJson = responseNode.toString();

                List<BloodGlucose> bloodGlucoses = new ArrayList<>();
                JsonPath bloodGlucosePath = JsonPath.compile("$.things[*].data-xml.blood-glucose");

                List<Object> hvbloodGlucoses = JsonPath.read(rawJson, bloodGlucosePath.getPath());
                if (CollectionUtils.isEmpty(hvbloodGlucoses)) {
                    return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, null);
                }
                ObjectMapper mapper = new ObjectMapper();
                for (Object fva : hvbloodGlucoses) {
                    JsonNode hvBloodGlucose = mapper.readTree(((JSONObject) fva).toJSONString());

                    DateTime dateTimeWhen = parseDateTimeFromWhenNode(hvBloodGlucose.get("when"));

                    String hvMeasureType = hvBloodGlucose.get("glucose-measurement-type").get("text").asText();
                    hvMeasureType = hvMeasureType.toLowerCase().replaceAll(" ", "_");

                    /**
                     * Must parse out the meal context properly, HV has many
                     * available choices, we care about fewer.
                     */
                    TemporalRelationshipToMeal mealContext = null;
                    if (hvBloodGlucose.get("measurement-context") != null) {
                        String hvMealContext = hvBloodGlucose.get("measurement-context").get("text").asText();
                        hvMealContext = hvMealContext.toLowerCase().trim();
                        if (hvMealContext.contains("after") && !hvMealContext.contains("exercise")) {
                            mealContext = TemporalRelationshipToMeal.after_meal;
                        } else if (hvMealContext.contains("before") && !hvMealContext.contains("exercise")) {
                            mealContext = TemporalRelationshipToMeal.before_meal;
                        } else if (hvMealContext.startsWith("non")) {
                            mealContext = TemporalRelationshipToMeal.not_fasting;
                        } else if (hvMealContext.contains("fasting")) {
                            mealContext = TemporalRelationshipToMeal.fasting;
                        }
                    }

                    bloodGlucoses.add(new BloodGlucoseBuilder().setTimeTaken(dateTimeWhen)
                            .setMgdLValue(
                                    new BigDecimal(hvBloodGlucose.get("value").get("display").get("").asText()))
                            .setBloodSpecimenType(BloodSpecimenType.valueOf(hvMeasureType))
                            .setTemporalRelationshipToMeal(
                                    mealContext != null ? TemporalRelationshipToMeal.valueOf(mealContext.toString())
                                            : null)
                            .build());
                }
                Map<String, Object> results = new HashMap<>();
                results.put(BloodGlucose.SCHEMA_BLOOD_GLUCOSE, bloodGlucoses);
                return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, results);
            }
        }),

        WEIGHT("3d34d87e-7fc1-4153-800f-f56592cb0d17", new JsonDeserializer<ShimDataResponse>() {

            @Override
            public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                    throws IOException {

                JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser);
                String rawJson = responseNode.toString();

                List<BodyWeight> bodyWeights = new ArrayList<>();
                JsonPath bodyWeightsPath = JsonPath.compile("$.things[*].data-xml.weight");

                List<Object> hvWeights = JsonPath.read(rawJson, bodyWeightsPath.getPath());
                if (CollectionUtils.isEmpty(hvWeights)) {
                    return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, null);
                }
                ObjectMapper mapper = new ObjectMapper();
                for (Object fva : hvWeights) {
                    JsonNode hvWeight = mapper.readTree(((JSONObject) fva).toJSONString());

                    DateTime dateTimeWhen = parseDateTimeFromWhenNode(hvWeight.get("when"));

                    BodyWeight bodyWeight = new BodyWeightBuilder()
                            .setWeight(hvWeight.get("value").get("display").get("").asText(),
                                    MassUnitValue.MassUnit.lb.toString())
                            .setTimeTaken(dateTimeWhen).build();

                    bodyWeights.add(bodyWeight);
                }
                Map<String, Object> results = new HashMap<>();
                results.put(BodyWeight.SCHEMA_BODY_WEIGHT, bodyWeights);
                return ShimDataResponse.result(HealthvaultShim.SHIM_KEY, results);
            }
        });

        private String dataTypeId;

        private JsonDeserializer<ShimDataResponse> normalizer;

        HealthVaultDataType(String dataTypeId, JsonDeserializer<ShimDataResponse> normalizer) {
            this.dataTypeId = dataTypeId;
            this.normalizer = normalizer;
        }

        public String getDataTypeId() {
            return dataTypeId;
        }

        public JsonDeserializer<ShimDataResponse> getNormalizer() {
            return normalizer;
        }
    }

    @Override
    public ShimDataResponse getData(final ShimDataRequest shimDataRequest) throws ShimException {
        final HealthVaultDataType healthVaultDataType;
        try {
            healthVaultDataType = HealthVaultDataType
                    .valueOf(shimDataRequest.getDataTypeKey().trim().toUpperCase());
        } catch (NullPointerException | IllegalArgumentException e) {
            throw new ShimException("Null or Invalid data type parameter: " + shimDataRequest.getDataTypeKey()
                    + " in shimDataRequest, cannot retrieve data.");
        }

        final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'hh:mm:ss");

        /***
         * Setup default date parameters
         */
        DateTime today = new DateTime();

        DateTime startDate = shimDataRequest.getStartDate() == null ? today.minusDays(1)
                : shimDataRequest.getStartDate();
        String dateStart = startDate.toString(formatter);

        DateTime endDate = shimDataRequest.getEndDate() == null ? today.plusDays(1) : shimDataRequest.getEndDate();
        String dateEnd = endDate.toString(formatter);

        long numToReturn = shimDataRequest.getNumToReturn() == null || shimDataRequest.getNumToReturn() <= 0 ? 100
                : shimDataRequest.getNumToReturn();

        Request request = new Request();
        request.setMethodName("GetThings");
        request.setInfo("<info>" + "<group max=\"" + numToReturn + "\">" + "<filter>" + "<type-id>"
                + healthVaultDataType.getDataTypeId() + "</type-id>" + "<eff-date-min>" + dateStart
                + "</eff-date-min>" + "<eff-date-max>" + dateEnd + "</eff-date-max>" + "</filter>" + "<format>"
                + "<section>core</section>" + "<xml/>" + "</format>" + "</group>" + "</info>");

        RequestTemplate template = new RequestTemplate(connection);
        return template.makeRequest(shimDataRequest.getAccessParameters(), request,
                new Marshaller<ShimDataResponse>() {
                    public ShimDataResponse marshal(InputStream istream) throws Exception {

                        /**
                         * XML Document mappings to JSON don't respect repeatable
                         * tags, they don't get properly serialized into collections.
                         * Thus, we pickup the 'things' via the 'group' root tag
                         * and create a new JSON document out of each 'thing' node.
                         */
                        XmlMapper xmlMapper = new XmlMapper();
                        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                        DocumentBuilder builder = factory.newDocumentBuilder();
                        Document doc = builder.parse(istream);
                        NodeList nodeList = doc.getElementsByTagName("thing");

                        /**
                         * Collect JsonNode from each 'thing' xml node.
                         */
                        List<JsonNode> thingList = new ArrayList<>();
                        for (int i = 0; i < nodeList.getLength(); i++) {
                            Node node = nodeList.item(i);
                            Document thingDoc = builder.newDocument();
                            Node newNode = thingDoc.importNode(node, true);
                            thingDoc.appendChild(newNode);
                            thingList.add(xmlMapper.readTree(convertDocumentToString(thingDoc)));
                        }

                        /**
                         * Rebuild JSON document structure to pass to deserializer.
                         */
                        String thingsJson = "{\"things\":[";
                        String thingsContent = "";
                        for (JsonNode thingNode : thingList) {
                            thingsContent += thingNode.toString() + ",";
                        }
                        thingsContent = "".equals(thingsContent) ? thingsContent
                                : thingsContent.substring(0, thingsContent.length() - 1);
                        thingsJson += thingsContent;
                        thingsJson += "]}";

                        /**
                         * Return raw re-built 'things' or a normalized JSON document.
                         */
                        ObjectMapper objectMapper = new ObjectMapper();
                        if (shimDataRequest.getNormalize()) {
                            SimpleModule module = new SimpleModule();
                            module.addDeserializer(ShimDataResponse.class, healthVaultDataType.getNormalizer());
                            objectMapper.registerModule(module);
                            return objectMapper.readValue(thingsJson, ShimDataResponse.class);
                        } else {
                            return ShimDataResponse.result(HealthvaultShim.SHIM_KEY,
                                    objectMapper.readTree(thingsJson));
                        }
                    }
                });
    }

    /**
     * Utility method for getting XML fragments required
     * for parsing XML docs from microsoft.
     *
     * @param doc - XML document fragment.
     * @return - Raw XML String.
     */
    private static String convertDocumentToString(Document doc) {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer;
        try {
            transformer = tf.newTransformer();
            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(doc), new StreamResult(writer));
            return writer.getBuffer().toString();
        } catch (TransformerException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public AuthorizationRequestParameters getAuthorizationRequestParameters(String username,
            Map<String, String> addlParameters) throws ShimException {

        String stateKey = OAuth1Utils.generateStateKey();
        AuthorizationRequestParameters authParams = new AuthorizationRequestParameters();
        authParams.setUsername(username);
        authParams.setStateKey(stateKey);

        //Callback URL
        String callbackUrl = shimServerConfig.getCallbackUrl(getShimKey(), stateKey);
        authParams.setAuthorizationUrl(getAuthorizationUrl(callbackUrl, ACTION_QS));

        authorizationRequestParametersRepo.save(authParams);

        return authParams;
    }

    @Override
    public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest servletRequest)
            throws ShimException {

        String stateKey = servletRequest.getParameter("state");

        AuthorizationRequestParameters authParams = authorizationRequestParametersRepo.findByStateKey(stateKey);
        if (authParams == null) {
            throw new ShimException("Invalid state, could not find " + "corresponding auth parameters");
        }

        // Fetch the access token.
        String accessToken = servletRequest.getParameter(TOKEN_PARAM);
        final String recordId = getSelectedRecordId(accessToken);

        AccessParameters accessParameters = new AccessParameters();
        accessParameters.setClientId(getClientId());
        accessParameters.setStateKey(stateKey);
        accessParameters.setUsername(authParams.getUsername());
        accessParameters.setAccessToken(accessToken);
        accessParameters.setAdditionalParameters(new HashMap<String, Object>() {
            {
                put(RECORD_ID_PARAM, recordId);
            }
        });

        return AuthorizationResponse.authorized(accessParameters);
    }

    private String getAuthorizationUrl(String redirectUrl, String actionQs) {
        return getBaseAuthorizeUrl() + "/redirect.aspx?target=AUTH&targetqs=?appid=" + getClientId()
                + "%26redirect=" + redirectUrl + "%26actionqs=" + actionQs;
    }

    /**
     * Retrieves specific recordId to look at in HealthVault.
     * Can be retrieved with the given authentication token
     * and is required on all subsequent requests.
     *
     * @param userAuthToken - User authentication token (i.e., wcToken)
     * @return - String representing the record id for a person.
     * @throws HVException
     */
    private String getSelectedRecordId(String userAuthToken) throws HVException {
        try {
            Request request = new Request();
            request.setTtl(3600 * 8 + 300);
            request.setMethodName("GetPersonInfo");
            request.setUserAuthToken(userAuthToken);
            HVAccessor accessor = new HVAccessor();
            accessor.send(request, ConnectionFactory.getConnection());
            InputStream is = accessor.getResponse().getInputStream();

            XPath xpath = XPathFactory.newInstance().newXPath();
            String exp = "//record/@id";
            return xpath.evaluate(exp, new InputSource(is));
        } catch (HVException he) {
            throw he;
        } catch (Exception e) {
            throw new HVException(e);
        }
    }

    /**
     * Utility for parsing a dateTime from 'when' xml node from
     * health vault.
     *
     * @param whenNode - The 'when' node of a 'thing' document.
     * @return - Joda DateTime corresponding to when node.
     */
    private static DateTime parseDateTimeFromWhenNode(JsonNode whenNode) {
        if (whenNode == null) {
            return null;
        }
        JsonNode dateNode = whenNode.get("date");
        JsonNode timeNode = whenNode.get("time");
        String dateString = dateNode.get("y").asText() + "-" + dateNode.get("m").asText() + "-"
                + dateNode.get("d").asText();
        if (timeNode != null) {
            String timeString = timeNode.get("h").asText() + ":" + timeNode.get("m").asText();
            return formatterMins.parseDateTime(dateString + " " + timeString);
        } else {
            return formatterDate.parseDateTime(dateString);
        }
    }

    /**
     * Utility interface for marshalling responses
     * from healthvault.
     *
     * @param <T>
     */
    private interface Marshaller<T> {
        T marshal(InputStream is) throws Exception;
    }

    /**
     * Utility class for making requests to healthvault.
     */
    private class RequestTemplate {
        Connection connection;

        public RequestTemplate(Connection connection) {
            this.connection = connection;
        }

        public Integer makeRequest(AccessParameters accessParameters, Request request) {
            return makeRequest(accessParameters, request, new Marshaller<Integer>() {
                public Integer marshal(InputStream is) {
                    return 0;
                }
            });
        }

        public <T> T makeRequest(AccessParameters accessInfo, Request request, Marshaller<T> marshaller) {
            request.setTtl(3600 * 8 + 300);
            request.setUserAuthToken(accessInfo.getAccessToken());
            request.setRecordId(accessInfo.getAdditionalParameters().get(RECORD_ID_PARAM).toString());
            HVAccessor accessor = new HVAccessor();
            accessor.send(request, ConnectionFactory.getConnection(config.getClientId()));
            try {
                InputStream istream = accessor.getResponse().getInputStream();
                return marshaller.marshal(istream);
            } catch (HVException e) {
                throw e;
            } catch (Exception e) {
                //TODO: need exception translator
                throw new HVException("Could not marshal response", e);
            }
        }
    }

    @Override
    public String getClientSecret() {
        return null; //NOOP
    }

    @Override
    public String getBaseTokenUrl() {
        return null; //NOOP
    }

    @Override
    public List<String> getScopes() {
        return null; //NOOP
    }
}