All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonservices.mws.client.MwsAQCall Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright 2009-2012 Amazon Services. All Rights Reserved.
 * 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://aws.amazon.com/apache2.0
 * This file 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.
 *******************************************************************************
 * Marketplace Web Service Runtime Client Library
 */
package com.amazonservices.mws.client;

import com.amazonservices.mws.client.MwsConnection.ServiceEndpoint;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.w3c.dom.Element;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.*;
import java.util.Map.Entry;

/**
 * An implementation of {@link MwsCall} that uses AWSQuery protocol.
 *
 * @author mayerj
 */
class MwsAQCall implements MwsCall {

    /** The connection to use. */
    private final MwsConnection connection;

    /** The name of the operation to perform. */
    private final String operationName;

    /** Container for parameters. */
    private final Map parameters = new TreeMap();

    /** The service endpoint from the servicePath. */
    private final ServiceEndpoint serviceEndpoint;

    /** The response header metadata from the call. */
    private MwsResponseHeaderMetadata rhmd;

    /**
     * Add authentication related and version parameter and set request body
     * with all of the parameters
     *
     * @param request
     */
    private void addRequiredParametersToRequest(HttpPost request) {
        parameters.put("Action", operationName);
        parameters.put("Version", serviceEndpoint.version);
        parameters.put("Timestamp", MwsUtl.getFormattedTimestamp());
        parameters.put("AWSAccessKeyId", connection.getAwsAccessKeyId());
        String signature = MwsUtl.signParameters(serviceEndpoint.uri,
                connection.getSignatureVersion(),
                connection.getSignatureMethod(), parameters,
                connection.getAwsSecretKeyId());
        parameters.put("Signature", signature);
        List parameterList = new ArrayList();
        for (Entry entry : parameters.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            if (!(key == null || key.equals("") || value == null || value
                    .equals(""))) {
                parameterList.add(new BasicNameValuePair(key, value));
            }
        }
        try {
            request.setEntity(new UrlEncodedFormEntity(parameterList, "UTF-8"));
        } catch (Exception e) {
            throw MwsUtl.wrap(e);
        }
    }

    /**
     * Read stream into string
     *
     * @param postResponse
     *            The response to get the body from.
     *
     * @return The response body.
     */
    private String getResponseBody(HttpResponse postResponse) {
        InputStream input = null;
        try {
            input = postResponse.getEntity().getContent();
            Reader reader = new InputStreamReader(input,
                    MwsUtl.DEFAULT_ENCODING);
            StringBuilder b = new StringBuilder();
            char[] c = new char[1024];
            int len;
            while (0 < (len = reader.read(c))) {
                b.append(c, 0, len);
            }
            return b.toString();
        } catch (Exception e) {
            throw MwsUtl.wrap(e);
        } finally {
            MwsUtl.close(input);
        }
    }

    /**
     * Get the metadata from the response headers.
     *
     * @param response
     *
     * @return The metadata.
     */
    private MwsResponseHeaderMetadata getResponseHeaderMetadata(HttpResponse response) {
        Header requestIdHeader = response.getFirstHeader("x-mws-request-id");
        String requestId = requestIdHeader == null ? null : requestIdHeader.getValue();

        Header timestampHeader = response.getFirstHeader("x-mws-timestamp");
        String timestamp = timestampHeader == null ? null : timestampHeader.getValue();

        Header contextHeader = response.getFirstHeader("x-mws-response-context");
        String contextString = contextHeader==null ? "" : contextHeader.getValue();
        List context = Collections.unmodifiableList(Arrays.asList(contextString.split(",")));

        Double quotaMax;
        try {
            Header quotaMaxHeader = response.getFirstHeader("x-mws-quota-max");
            quotaMax = quotaMaxHeader == null ? null : Double.valueOf(quotaMaxHeader.getValue());
        } catch (NumberFormatException ex) {
            quotaMax = null;
        }

        Double quotaRemaining;
        try {
            Header quotaRemainingHeader = response.getFirstHeader("x-mws-quota-remaining");
            quotaRemaining = quotaRemainingHeader == null ? null : Double.valueOf(quotaRemainingHeader.getValue());
        } catch (NumberFormatException ex) {
            quotaRemaining = null;
        }

        Date quotaResetDate;
        try {
            Header quotaResetHeader = response.getFirstHeader("x-mws-quota-resetsOn");
            quotaResetDate = quotaResetHeader == null ? null : MwsUtl.parseTimestamp(quotaResetHeader.getValue());
        } catch (java.text.ParseException ex) {
            quotaResetDate = null;
        }

        return new MwsResponseHeaderMetadata(requestId, context, timestamp, quotaMax, quotaRemaining, quotaResetDate);
    }

    /**
     * Random exponential back-off sleep on failed request.
     *
     * If retry needed sleeps and then return true.
     *
     * Sleep is random so that retry requests do not form spikes.
     *
     * @param retries
     *            current retry, 0 for first retry
     *
     * @return true if should retry.
     */
    private boolean pauseIfRetryNeeded(int retries) {
        if (retries >= connection.getMaxErrorRetry()) {
            return false;
        }
        long delay = (long) (Math.random() * Math.pow(4, retries) * 125);
        try {
            Thread.sleep(delay);
        } catch (Exception e) {
            throw MwsUtl.wrap(e);
        }
        return true;
    }

    @Override
    public void beginObject(String name) {
        parameterPrefix.append(name);
        parameterPrefix.append('.');
    }

    @Override
    public void close() {
        // close does not need to do anything here.
    }

    @Override
    public void endObject(String name) {
        int nlen = name.length();
        int pplen = parameterPrefix.length();
        if (pplen header : connection.getRequestHeaders().entrySet()) {
                request.addHeader(header.getKey(), header.getValue());
            }
            addRequiredParametersToRequest(request);
        } catch (Exception e) {
            request.releaseConnection();
            throw MwsUtl.wrap(e);
        }
        return request;
    }

    /**
     * Execute a request on this calls connection and context.
     *
     * @param request
     *
     * @return The response to executing the request.
     */
    private HttpResponse executeRequest(HttpPost request) throws Exception {
        try {
            HttpClient httpClient = connection.getHttpClient();
            HttpContext httpContext = connection.getHttpContext();
            HttpResponse response = httpClient.execute(request, httpContext);
            return response;
        } catch (Exception e) {
            throw MwsUtl.wrap(e);
        }
    }

    /**
     * Perform a synchronous call with no retry or error handling.
     *
     * @return
     */
    @Override
    public MwsResponse execute() {
        HttpPost request = createRequest();
        try {
            HttpResponse hr = executeRequest(request);
            StatusLine statusLine = hr.getStatusLine();
            int status = statusLine.getStatusCode();
            String message = statusLine.getReasonPhrase();
            rhmd = getResponseHeaderMetadata(hr);
            String body = getResponseBody(hr);
            MwsResponse response = new MwsResponse(status,message,rhmd,body);
            return response;
        } catch (Exception e) {
            throw MwsUtl.wrap(e);
        } finally {
             request.releaseConnection();
        }
    }

    /**
     * Perform a synchronous call with error handling and back-off/retry.
     *
     * @return A MwsReader to read the response from.
     */
    @Override
    public MwsReader invoke() {
        HttpPost request = createRequest();
        for (int retryCount = 0;;retryCount++) {
            try {
                HttpResponse response = executeRequest(request);
                StatusLine statusLine = response.getStatusLine();
                int status = statusLine.getStatusCode();
                String message = statusLine.getReasonPhrase();
                rhmd = getResponseHeaderMetadata(response);
                String body = getResponseBody(response);
                if (status == HttpStatus.SC_OK) {
                    return new MwsXmlReader(body);
                }
                if (status == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
                    if (pauseIfRetryNeeded(retryCount)) {
                        continue;
                    }
                }
                throw new MwsException(status, message, null, null, body, rhmd);
            } catch (Exception e) {
                throw MwsUtl.wrap(e);
            } finally {
                request.releaseConnection();
            }
        }
    }

    /** The parameter prefix */
    private StringBuilder parameterPrefix = new StringBuilder();

    /**
     * Put a value into the parameters.
     * 

* Uses parameterPrefix, suppresses nulls and empty string values. *

* May have side effect of appending to parameterPrefix. * * @param value */ private void putValue(Object value) { if (value==null) { return; } if (value instanceof MwsObject) { parameterPrefix.append('.'); ((MwsObject)value).writeFragmentTo(this); return; } String svalue = value.toString(); if (svalue!=null && svalue.length()>0) { String name = parameterPrefix.toString(); parameters.put(name, svalue); } } @Override public void write(String name, Object value) { int holdParameterPrefixLen = parameterPrefix.length(); parameterPrefix.append(name); putValue(value); parameterPrefix.setLength(holdParameterPrefixLen); } @Override public void write(String namespace, String name, MwsObject value) { value.writeFragmentTo(this); } @Override public void writeAttribute(String name, Object value) { write(name, value); } @Override public void writeList(String name, String memberName, Collection list) { if (name==null && memberName==null) { throw new RuntimeException("Both name and memberName cannot be null."); } if (list == null) { return; } int holdParameterPrefixLen = parameterPrefix.length(); if (name!=null) { parameterPrefix.append(name); } if (name!=null && memberName!=null) { parameterPrefix.append('.'); } if (memberName!=null) { parameterPrefix.append(memberName); } parameterPrefix.append('.'); int dotLen = parameterPrefix.length(); int i = 1; for (Object value : list) { parameterPrefix.setLength(dotLen); parameterPrefix.append(i); putValue(value); i++; } parameterPrefix.setLength(holdParameterPrefixLen); } @Override public void writeList(String name, Collection list) { writeList(null, name, list); } @Override public void writeAny(Collection elements) { throw new RuntimeException("writeAny not supported."); } @Override public void writeValue(Object value) { throw new RuntimeException("writeValue not supported."); } /** * Start a new request on the operation. * * @param connection * The connection to use to connect to the end-point. * * @param serviceEndpoint * The service endpoint. * * @param operationName * The operation name. */ MwsAQCall(MwsConnection connection, ServiceEndpoint serviceEndpoint, String operationName) { this.connection = connection; this.serviceEndpoint = serviceEndpoint; this.operationName = operationName; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy