/* ================================================================== * JsonHttpClientSupport.java - Oct 6, 2014 12:35:03 PM * * Copyright 2007-2014 SolarNetwork.net Dev Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * ================================================================== */ package net.solarnetwork.node.support; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.zip.GZIPOutputStream; import net.solarnetwork.node.RemoteServiceException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** * An abstract class to support HTTP based services that use JSON. * * <p> * The configurable properties of this class are: * </p> * * <dl class="class-properties"> * <dt>compress</dt> * <dd>Flag to compress the HTTP body content, defaults to <em>false</em>.</dd> * * <dt>objectMapper</dt> * <dd>The {@link ObjectMapper} to marshall/unmarshall objects to/from JSON * with.</dd> * </dl> * * @author matt * @version 1.1 */ public abstract class JsonHttpClientSupport extends HttpClientSupport { /** The JSON MIME type. */ public static final String JSON_MIME_TYPE = "application/json"; private ObjectMapper objectMapper; private boolean compress = false; /** * Perform a JSON HTTP request. * * @param url * the URL to make the request to * @param method * the HTTP method, e.g. {@link HttpClientSupport#HTTP_METHOD_GET} * @param data * the optional data to marshall to JSON and upload as the request * content * @return the InputStream for the HTTP response * @throws IOException * if any IO error occurs */ protected final InputStream doJson(String url, String method, Object data) throws IOException { URLConnection conn = getURLConnection(url, method, JSON_MIME_TYPE); if ( data != null ) { conn.setRequestProperty("Content-Type", JSON_MIME_TYPE + ";charset=UTF-8"); if ( compress ) { conn.setRequestProperty("Content-Encoding", "gzip"); } OutputStream out = conn.getOutputStream(); if ( compress ) { out = new GZIPOutputStream(out); } if ( log.isDebugEnabled() ) { log.debug("Posting JSON data: {}", objectMapper.writerWithDefaultPrettyPrinter() .writeValueAsString(data)); } objectMapper.writeValue(out, data); out.flush(); out.close(); } return getInputStreamFromURLConnection(conn); } /** * Perform a JSON GET HTTP request. * * @param url * the URL to GET * @return the HTTP response InputStream * @throws IOException * if any IO error occurs */ protected final InputStream jsonGET(String url) throws IOException { return doJson(url, HTTP_METHOD_GET, null); } /** * Perform a JSON POST HTTP request. * * @param url * the URL to POST * @param data * the object to marshall into JSON * @return the HTTP response InputStream * @throws IOException * if any IO error occurs */ protected final InputStream jsonPOST(String url, Object data) throws IOException { return doJson(url, HTTP_METHOD_POST, data); } /** * Parse a standard {@code Response} HTTP response and return the * {@code data} object as the provided type. * * @param in * the InputStream to read, which will be closed before returning * from this method * @param dataType * the type of object to extract from the response * @return the extracted object, or <em>null</em> * @throws RemoteServiceException * if the response does not include the success flag * @throws IOException * if any IO error occurs */ protected <T> T extractResponseData(InputStream in, Class<T> dataType) throws RemoteServiceException, IOException { try { JsonNode root = getObjectMapper().readTree(in); if ( root.isObject() ) { JsonNode child = root.get("success"); if ( child != null && child.asBoolean() ) { child = root.get("data"); if ( child != null ) { return objectMapper.treeToValue(child, dataType); } log.debug("Server returned no data for request."); return null; } } throw new RemoteServiceException("Server response not successful: " + (root.get("message") == null ? "(no message)" : root.get("message").asText())); } finally { if ( in != null ) { in.close(); } } } /** * Parse a standard {@code Response} HTTP response and return the * {@code data} object as the provided type. * * @param in * the InputStream to read, which will be closed before returning * from this method * @param dataType * the type of object to extract from the response * @return the extracted object, or <em>null</em> * @throws RemoteServiceException * if the response does not include the success flag * @throws IOException * if any IO error occurs * @since 1.1 */ protected <T> Collection<T> extractCollectionResponseData(InputStream in, Class<T> dataType) throws RemoteServiceException, IOException { try { JsonNode root = getObjectMapper().readTree(in); if ( root.isObject() ) { JsonNode child = root.get("success"); if ( child != null && child.asBoolean() ) { child = root.get("data"); if ( child != null && child.isArray() ) { Iterator<JsonNode> children = child.iterator(); List<T> result = new ArrayList<T>(); while ( children.hasNext() ) { child = children.next(); result.add(objectMapper.treeToValue(child, dataType)); } return result; } log.debug("Server returned no data for request."); return null; } } throw new RemoteServiceException( "Server response not successful: " + root.get("message") == null ? "(no message)" : root.get("message").asText()); } finally { if ( in != null ) { in.close(); } } } /** * Test for a successful standard {@code Response} HTTP response. * * @param in * the InputStream to read, which will be closed before returning * from this method * @throws RemoteServiceException * if the response does not include the success flag * @throws IOException * if any IO error occurs */ protected void verifyResponseSuccess(InputStream in) throws RemoteServiceException, IOException { try { JsonNode root = getObjectMapper().readTree(in); if ( root.isObject() ) { JsonNode child = root.get("success"); if ( child != null && child.asBoolean() ) { return; } } throw new RemoteServiceException( "Server response not successful: " + root.get("message") == null ? "(no message)" : root.get("message").asText()); } finally { if ( in != null ) { in.close(); } } } public final ObjectMapper getObjectMapper() { return objectMapper; } public final void setObjectMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public final boolean isCompress() { return compress; } public final void setCompress(boolean compress) { this.compress = compress; } }