package com.mozu.client; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.mozu.api.ApiContext; import com.mozu.api.ApiError; import com.mozu.api.ApiException; import com.mozu.api.Headers; import com.mozu.api.MozuClient; import com.mozu.api.MozuConfig; import com.mozu.api.MozuUrl; import com.mozu.api.Version; import com.mozu.api.ApiError.Item; import com.mozu.api.cache.db.CacheItem; import com.mozu.api.cache.db.CacheManager; import com.mozu.api.contracts.tenant.Tenant; import com.mozu.api.resources.platform.TenantResource; import com.mozu.api.security.AppAuthenticator; import com.mozu.api.security.AuthTicket; import com.mozu.api.security.AuthenticationScope; import com.mozu.api.security.CustomerAuthenticator; import com.mozu.api.security.UserAuthenticator; import com.mozu.api.utils.HttpHelper; import com.mozu.api.utils.JsonUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpMessage; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @SuppressWarnings("deprecation") public class MozuClientImpl<TResult> implements MozuClient<TResult> { private static final ObjectMapper mapper = JsonUtils.initObjectMapper(); // static private HttpHost proxyHttpHost = HttpHelper.getProxyHost(); private ApiContext apiContext = null; private String baseAddress = null; private HttpResponse httpResponseMessage = null; private String httpContent = null; private InputStream streamContent = null; private String verb = null; private MozuUrl resourceUrl = null; private HashMap<String, String> headers = new HashMap<String, String>(); private final Class<TResult> responseType; private String mCacheKey; /** * Charset for request. */ private static final String PROTOCOL_CHARSET = "utf-8"; /** * Content type for request. */ private static final String PROTOCOL_CONTENT_TYPE = String.format("application/json; charset=%s", PROTOCOL_CHARSET); private static final String HEADER_CONTENT_TYPE = "Content-Type"; public MozuClientImpl() { this.responseType = null; } public MozuClientImpl(Class<TResult> responseType) throws Exception { this.responseType = responseType; } public void setContext(ApiContext apiContext) { this.apiContext = apiContext; if (apiContext != null) { if (apiContext.getTenantId() > 0) { addHeader(Headers.X_VOL_TENANT, String.valueOf(apiContext.getTenantId())); } if (apiContext.getSiteId() != null && apiContext.getSiteId() > 0) { addHeader(Headers.X_VOL_SITE, String.valueOf(apiContext.getSiteId())); } if (apiContext.getMasterCatalogId() != null && apiContext.getMasterCatalogId() > 0) { addHeader(Headers.X_VOL_MASTER_CATALOG, String.valueOf(apiContext.getMasterCatalogId())); } if (apiContext.getCatalogId() != null && apiContext.getCatalogId() > 0) { addHeader(Headers.X_VOL_CATALOG, String.valueOf(apiContext.getCatalogId())); } if (apiContext.getLocale() != null) { addHeader(Headers.X_VOL_LOCALE, String.valueOf(apiContext.getLocale())); } if (apiContext.getCurrency() != null) { addHeader(Headers.X_VOL_CURRENCY, String.valueOf(apiContext.getCurrency())); } } } public void setBaseAddress(String baseAddress) { this.baseAddress = baseAddress; } public void addHeader(String header, String value) { headers.put(header, value); } public void setVerb(String verb) { this.verb = verb; } public void setResourceUrl(MozuUrl resourceUrl) { this.resourceUrl = resourceUrl; } public <TBody> void setBody(TBody body) throws JsonProcessingException { httpContent = mapper.writeValueAsString(body); } public void setBody(InputStream body) throws JsonProcessingException { streamContent = body; } public void setBody(String body) { httpContent = body; } public String getStringResult() throws Exception { return stringContent(); } @SuppressWarnings("unchecked") public TResult getResult() throws Exception { TResult tResult = null; if (responseType != null) { String className = responseType.getName(); if (className.equals(java.io.InputStream.class.getName())) { tResult = (TResult) httpResponseMessage.getEntity().getContent(); } else { CacheItem cacheItem; if (mCacheKey != null && (cacheItem = CacheManager.checkAndGetCache(mCacheKey)) != null) { tResult = deserialize(cacheItem.getContent(), responseType); } else { tResult = deserialize(getStringResult(), responseType); } } } return tResult; } public HttpResponse getResponse() { return httpResponseMessage; } protected void validateContext() throws Exception { if (resourceUrl.getLocation() == MozuUrl.UrlLocation.TENANT_POD) { if (apiContext == null || apiContext.getTenantId() <= 0) throw new ApiException("TenantId is missing"); if (StringUtils.isBlank(apiContext.getTenantUrl())) { TenantResource tenantResource = new TenantResource(); Tenant tenant = tenantResource.getTenant(apiContext.getTenantId()); if (tenant == null) throw new ApiException("Tenant " + apiContext.getTenantId() + " Not found"); baseAddress = HttpHelper.getUrl(tenant.getDomain()); } else { baseAddress = apiContext.getTenantUrl(); } } else if (resourceUrl.getLocation() == MozuUrl.UrlLocation.HOME_POD) { AppAuthenticator appAuthenticator = AppAuthenticator.getInstance(); if (appAuthenticator == null) { throw new ApiException("Application has not been authorized to access Mozu."); } else if (StringUtils.isBlank(MozuConfig.getBaseUrl())) { throw new ApiException("Authentication.Instance.BaseUrl is missing"); } baseAddress = MozuConfig.getBaseUrl(); } else if (resourceUrl.getLocation() == MozuUrl.UrlLocation.PCI_POD) { if (apiContext == null || apiContext.getTenantId() < 0) { throw new ApiException("TenantId is missing"); } if (StringUtils.isBlank(MozuConfig.getBasePciUrl())) { throw new ApiException("Authentication.Instance.BasePciUrl is missing"); } baseAddress = MozuConfig.getBasePciUrl(); } } private String getCacheKey(URL url, Map<String, String> requestHeaders) { StringBuilder key = new StringBuilder(); key.append(url); if (apiContext != null) { if (apiContext.getSiteId() != null) key.append(apiContext.getSiteId()); if (!StringUtils.isEmpty(apiContext.getCurrency())) key.append(apiContext.getCurrency()); if (!StringUtils.isEmpty(apiContext.getLocale())) key.append(apiContext.getLocale()); if (apiContext.getMasterCatalogId() != null) key.append(apiContext.getMasterCatalogId()); if (apiContext.getCatalogId() != null) key.append(apiContext.getCatalogId()); String dataViewMode = requestHeaders.get(Headers.X_VOL_DATAVIEW_MODE); if (!StringUtils.isEmpty(dataViewMode)) key.append(dataViewMode); } return key.toString(); } /** * Create an {@link HttpURLConnection} for the specified {@code url}. */ protected HttpURLConnection createConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } /** * Opens an {@link java.net.HttpURLConnection} with parameters. * * @param url * @return an open connection * @throws IOException */ private HttpURLConnection openConnection(URL url) throws IOException { HttpURLConnection connection = createConnection(url); connection.setUseCaches(true); connection.setDoInput(true); return connection; } private static HttpEntity entityFromConnection(HttpURLConnection connection) { BasicHttpEntity entity = new BasicHttpEntity(); InputStream inputStream; try { inputStream = connection.getInputStream(); } catch (IOException ioe) { inputStream = connection.getErrorStream(); } entity.setContent(inputStream); entity.setContentLength(connection.getContentLength()); entity.setContentEncoding(connection.getContentEncoding()); entity.setContentType(connection.getContentType()); return entity; } @SuppressWarnings("unchecked") private void setCache() throws Exception { CacheItem cacheItem = CacheManager.checkAndGetCache(mCacheKey); String eTag = getHeaderValue("ETag", httpResponseMessage); if (cacheItem != null && httpResponseMessage.getStatusLine().getStatusCode() == 304) { } else if (StringUtils.isNotEmpty(eTag) && httpResponseMessage.getStatusLine().getStatusCode() != 404) { CacheManager.insertCacheItem(eTag, mCacheKey, stringContent()); } } public void executeRequest() throws Exception { validateContext(); String url = baseAddress + resourceUrl.getUrl(); if (apiContext != null && apiContext.getUserAuthTicket() != null) { setUserClaims(); } addHeader(Headers.X_VOL_APP_CLAIMS, AppAuthenticator.addAuthHeader()); addHeader(Headers.X_VOL_VERSION, Version.API_VERSION); httpResponseMessage = performRequest(url, headers); ensureSuccess(httpResponseMessage, mapper); setCache(); } private HttpResponse performRequest(String url, Map<String, String> requestHeaders) throws Exception { URL parsedURL = new URL(url); HttpURLConnection connection = openConnection(parsedURL); buildConnection(connection, requestHeaders); // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); int responseCode = connection.getResponseCode(); if (responseCode == -1) { // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromConnection(connection)); for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response; } private void buildConnection(HttpURLConnection connection, Map<String, String> requestHeaders) throws Exception { if (requestHeaders != null) { Iterator<Entry<String, String>> i = requestHeaders.entrySet().iterator(); while (i.hasNext()) { Entry<String, String> header = i.next(); connection.setRequestProperty(header.getKey(), header.getValue()); } if (!requestHeaders.containsKey(Headers.CONTENT_TYPE)) { connection.setRequestProperty(Headers.CONTENT_TYPE, PROTOCOL_CONTENT_TYPE); } } connection.setRequestProperty("Accept", "application/json"); mCacheKey = getCacheKey(connection.getURL(), requestHeaders); CacheItem cacheItem = CacheManager.checkAndGetCache(mCacheKey); if (cacheItem != null) { connection.setRequestProperty("If-None-Match", cacheItem.geteTag()); } connection.setRequestMethod(verb); if (verb.equals("POST") || verb.equals("PUT")) { if (StringUtils.isNotBlank(httpContent)) { addBodyIfExists(connection, httpContent); } else if (this.streamContent != null) { addBodyIfExists(connection, streamContent); } } } private TResult executeRequest(Object bodyObject, String url, String verb, Map<String, String> additionalHeaders) { TResult result = null; try { String body = mapper.writeValueAsString(bodyObject); setBody(body); setVerb(verb); httpResponseMessage = performRequest(url, additionalHeaders); ensureSuccess(httpResponseMessage, mapper); result = getResult(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (JsonProcessingException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; } private TResult deserialize(String jsonString, Class<TResult> cls) throws Exception { return mapper.readValue(jsonString, cls); } private String stringContent() throws Exception { HttpEntity httpEntity = httpResponseMessage.getEntity(); InputStream stream = (InputStream) httpEntity.getContent(); return org.apache.commons.io.IOUtils.toString(stream, "UTF-8"); } public byte[] getBody(String mRequestBody) { try { return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET); } catch (UnsupportedEncodingException uee) { return null; } } private void addBodyIfExists(HttpURLConnection connection, String body) throws IOException { byte[] requestBody = getBody(body); if (requestBody != null) { connection.setDoOutput(true); if (connection.getRequestProperty(HEADER_CONTENT_TYPE) == null) { connection.addRequestProperty(HEADER_CONTENT_TYPE, PROTOCOL_CONTENT_TYPE); } DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(requestBody); out.close(); } } private void addBodyIfExists(HttpURLConnection connection, InputStream body) throws IOException { byte[] requestBody = IOUtils.toByteArray(body); if (requestBody != null) { connection.setDoOutput(true); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(requestBody); out.close(); } } private void setUserClaims() { AuthTicket newAuthTicket = null; if (apiContext.getUserAuthTicket().getScope() == AuthenticationScope.Customer) newAuthTicket = CustomerAuthenticator.ensureAuthTicket(apiContext.getUserAuthTicket(), apiContext.getTenantId(), apiContext.getSiteId()); else newAuthTicket = UserAuthenticator.ensureAuthTicket(apiContext.getUserAuthTicket()); if (newAuthTicket != null) { apiContext.getUserAuthTicket().setAccessToken(newAuthTicket.getAccessToken()); apiContext.getUserAuthTicket().setAccessTokenExpiration( newAuthTicket.getAccessTokenExpiration()); } addHeader(Headers.X_VOL_USER_CLAIMS, apiContext.getUserAuthTicket().getAccessToken()); } protected Map<String, String> getHeaders() { return headers; } @Override public TResult executePostRequest(Object bodyObject, String resourceUrl, Map<String, String> headers) throws ApiException { return this.executeRequest(bodyObject, resourceUrl, "POST", headers); } @Override public TResult executePutRequest(Object bodyObject, String resourceUrl, Map<String, String> headers) throws ApiException { return this.executeRequest(bodyObject, resourceUrl, "PUT", headers); } @Override public void executeDeleteRequest(String resourceUrl, Map<String, String> headers) throws ApiException { HttpDelete delete = new HttpDelete(resourceUrl); delete.setHeader("Accept", "application/json"); delete.setHeader("Content-type", "application/json"); if (headers!=null) { for (Map.Entry<String, String> entry: headers.entrySet()) { delete.addHeader(entry.getKey(), entry.getValue()); } } try { executeRequest(delete); } catch (Exception ioe) { throw new ApiException("Exception occurred while authenticating user: " + ioe.getMessage()); } } @Override public void cleanupHttpConnection() throws Exception { } private void executeRequest(HttpRequestBase request) throws Exception { HttpClient client = new DefaultHttpClient(); httpResponseMessage = client.execute(request); try { ensureSuccess(httpResponseMessage, mapper); } catch (Exception e) { // make sure on exception that that response is closed throw e; } } protected void ensureSuccess(HttpResponse response, ObjectMapper mapper) throws ApiException { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 304 && (statusCode < 200 || statusCode > 300)) { try { ApiError apiError = mapper.readValue(response.getEntity().getContent(), ApiError.class); apiError.setCorrelationId(getHeaderValue(Headers.X_VOL_CORRELATION, response)); throw new ApiException(getMozuErrorMessage(apiError), apiError, statusCode); } catch (JsonProcessingException jpe) { throw new ApiException("An error has occurred. Status Code: " + statusCode + " Status Message: " + response.getStatusLine().getReasonPhrase(), statusCode); } catch (IOException ioe) { throw new ApiException("An error occurred. Status Code: " + statusCode + " Status Message: " + response.getStatusLine().getReasonPhrase(), statusCode); } } } private static String getMozuErrorMessage(ApiError apiError) { StringBuilder errorMessage = new StringBuilder("Error returned from Mozu. Correlation ID: "); errorMessage.append(apiError.getCorrelationId()); errorMessage.append(". Message: "); if (StringUtils.isNotBlank(apiError.getMessage())) { errorMessage.append(apiError.getMessage()); } else if (apiError.getExceptionDetail() != null && StringUtils.isNotBlank(apiError.getExceptionDetail().getMessage())) { errorMessage.append(apiError.getExceptionDetail().getMessage()); } else if (apiError.getItems().size() > 0) { for (Item item : apiError.getItems()) { errorMessage.append(item.getMessage()); errorMessage.append(". Error Code: ").append(item.getErrorCode()); } } else { errorMessage.append("No error message returned from Mozu."); } return errorMessage.toString(); } private String getHeaderValue(String header, HttpURLConnection connection) { return connection.getHeaderField(header); } private String getHeaderValue(String header, HttpMessage httpMessage) { if (httpMessage.containsHeader(header)) return (httpMessage.getFirstHeader(header)).getValue(); else return null; } }