/***************************************************************************** * * Copyright (C) Zenoss, Inc. 2010, all rights reserved. * * This content is made available according to terms specified in * License.zenoss under the directory where your Zenoss product is installed. * ****************************************************************************/ package org.zenoss.zep.rest; import com.google.protobuf.Message; import org.apache.http.Header; import org.apache.http.HeaderIterator; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zenoss.protobufs.JsonFormat; import org.zenoss.protobufs.ProtobufConstants; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; public class RestClient implements Closeable { @SuppressWarnings("unused") private static final Logger logger = LoggerFactory .getLogger(RestClient.class); private HttpClient client = new DefaultHttpClient(); private Map<String, Message> supportedMessages = new HashMap<String, Message>(); private String host = System.getProperty("jetty.host", "localhost"); private String port = System.getProperty("jetty.port", "8084"); public static class RestResponse { private final HttpResponse response; private final int responseCode; private final Message message; private final Map<String, List<String>> headers; public RestResponse(final HttpResponse response, final Message message) { this.response = response; this.responseCode = response.getStatusLine().getStatusCode(); this.message = message; Map<String, List<String>> headers = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER); HeaderIterator it = response.headerIterator(); while (it.hasNext()) { Header header = it.nextHeader(); List<String> l = headers.get(header.getName()); if (l == null) { l = new ArrayList<String>(); headers.put(header.getName(), l); } l.add(header.getValue()); } this.headers = headers; } public HttpResponse getResponse() { return this.response; } public int getResponseCode() { return responseCode; } public Message getMessage() { return message; } public Map<String, List<String>> getHeaders() { return Collections.unmodifiableMap(this.headers); } @Override public String toString() { return "RestResponse{" + "responseCode=" + responseCode + ", message=" + message + ", headers=" + headers + '}'; } } public RestClient(Message... msgs) { for (Message msg : msgs) { this.supportedMessages.put( msg.getDescriptorForType().getFullName(), msg); } } private URI createURI(String path) throws IOException { try { if (path.startsWith("/")) { return new URI("http", null, host, Integer.valueOf(port), path, null, null); } return new URI(path); } catch (URISyntaxException e) { throw new IOException(e); } } private HttpEntity createProtobufEntity(Message msg) { BasicHttpEntity entity = new BasicHttpEntity(); entity.setContentType(ProtobufConstants.CONTENT_TYPE_PROTOBUF); entity.setContentLength(msg.getSerializedSize()); entity.setContent(new ByteArrayInputStream(msg.toByteArray())); return entity; } private HttpEntity createJsonEntity(Message msg) throws IOException { BasicHttpEntity entity = new BasicHttpEntity(); entity.setContentType(MediaType.APPLICATION_JSON); ByteArrayOutputStream baos = new ByteArrayOutputStream(); JsonFormat.writeTo(msg, baos); entity.setContent(new ByteArrayInputStream(baos.toByteArray())); return entity; } private Message createProtobufFromResponse(HttpResponse response) throws IllegalStateException, IOException { HttpEntity entity = response.getEntity(); Message msg = null; int responseCode = response.getStatusLine().getStatusCode(); Header fullNameHeader = response .getFirstHeader(ProtobufConstants.HEADER_PROTOBUF_FULLNAME); if (fullNameHeader != null) { Message msgForBuild = this.supportedMessages.get(fullNameHeader .getValue()); if (msgForBuild == null) { throw new IOException("Unsupported message type: " + fullNameHeader.getValue()); } String contentType = response.getFirstHeader("Content-Type") .getValue(); if (MediaType.APPLICATION_JSON.equals(contentType)) { msg = JsonFormat.merge(response.getEntity().getContent(), msgForBuild.newBuilderForType()); } else if (ProtobufConstants.CONTENT_TYPE_PROTOBUF.equals(contentType)) { msg = msgForBuild.newBuilderForType() .mergeFrom(response.getEntity().getContent()).build(); } else { throw new IOException("Unsupported content type: " + contentType); } } else if (responseCode != HttpStatus.SC_NO_CONTENT && responseCode != HttpStatus.SC_CREATED) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); entity.writeTo(baos); throw new IOException(String.format("Status code: %d, Response: %s", responseCode, baos.toString("UTF-8"))); } entity.consumeContent(); return msg; } public RestResponse get(String path, String acceptContent) throws IOException { Message msg = null; HttpGet method = new HttpGet(createURI(path)); method.addHeader("Accept", acceptContent); HttpResponse response = this.client.execute(method); int rc = response.getStatusLine().getStatusCode(); if (rc == 200) { msg = createProtobufFromResponse(response); } else if (response.getStatusLine().getStatusCode() != 404) { throw new IOException("Unexpected RC: " + rc); } return new RestResponse(response, msg); } public RestResponse getJson(String path) throws IOException { return get(path, MediaType.APPLICATION_JSON); } public RestResponse getProtobuf(String path) throws IOException { return get(path, ProtobufConstants.CONTENT_TYPE_PROTOBUF); } public RestResponse post(String path, Message msg, String contentType) throws IOException { HttpPost post = new HttpPost(createURI(path)); post.addHeader(ProtobufConstants.HEADER_PROTOBUF_FULLNAME, msg .getDescriptorForType().getFullName()); final HttpEntity entity; if (MediaType.APPLICATION_JSON.equals(contentType)) { entity = createJsonEntity(msg); } else if (ProtobufConstants.CONTENT_TYPE_PROTOBUF.equals(contentType)) { entity = createProtobufEntity(msg); } else { throw new IllegalArgumentException("Unsupported content type"); } post.setEntity(entity); HttpResponse response = client.execute(post); Message msgRsp = null; if (isNonNullProtoBuf(response)) { msgRsp = createProtobufFromResponse(response); } return new RestResponse(response, msgRsp); } private boolean isNonNullProtoBuf(final HttpResponse response) { if (response.getEntity() == null || response.getFirstHeader(HttpHeaders.CONTENT_TYPE) == null) { return false; } String contentType = response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue(); List<String> protoBufTypes = Arrays.asList(MediaType.APPLICATION_JSON, ProtobufConstants.CONTENT_TYPE_PROTOBUF); return protoBufTypes.contains(contentType); } public RestResponse postJson(String path, Message msg) throws IOException { return post(path, msg, MediaType.APPLICATION_JSON); } public RestResponse postProtobuf(String path, Message msg) throws IOException { return post(path, msg, ProtobufConstants.CONTENT_TYPE_PROTOBUF); } public RestResponse put(String path, Message msg, String contentType) throws IOException { HttpPut put = new HttpPut(createURI(path)); put.addHeader(ProtobufConstants.HEADER_PROTOBUF_FULLNAME, msg .getDescriptorForType().getFullName()); final HttpEntity entity; if (MediaType.APPLICATION_JSON.equals(contentType)) { entity = createJsonEntity(msg); } else if (ProtobufConstants.CONTENT_TYPE_PROTOBUF.equals(contentType)) { entity = createProtobufEntity(msg); } else { throw new IllegalArgumentException("Unsupported content type"); } put.setEntity(entity); HttpResponse response = client.execute(put); Message msgRsp = null; if (response.getEntity() != null) { msgRsp = createProtobufFromResponse(response); } return new RestResponse(response, msgRsp); } public RestResponse putJson(String path, Message msg) throws IOException { return put(path, msg, MediaType.APPLICATION_JSON); } public RestResponse putProtobuf(String path, Message msg) throws IOException { return put(path, msg, ProtobufConstants.CONTENT_TYPE_PROTOBUF); } public RestResponse delete(String path) throws IOException { HttpResponse response = this.client.execute(new HttpDelete(createURI(path))); if (response.getEntity() != null) { response.getEntity().consumeContent(); } return new RestResponse(response, null); } @Override public void close() throws IOException { this.client.getConnectionManager().shutdown(); } }