You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ne...@apache.org on 2018/06/25 22:07:00 UTC

[trafficcontrol] 01/12: Added Java API foundations. Added foundation for TrafficOps java api

This is an automated email from the ASF dual-hosted git repository.

neuman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 213f65b9e64dc3db8e04c326078a3aa3dfb0358b
Author: nerdynick <ne...@gmail.com>
AuthorDate: Tue Dec 5 09:40:53 2017 -0700

    Added Java API foundations. Added foundation for TrafficOps java api
---
 traffic_control/clients/java/.gitignore            |   4 +
 traffic_control/clients/java/common/.gitignore     |   1 +
 traffic_control/clients/java/common/pom.xml        |  29 ++++
 .../cdn/traffic_control/RestApiSession.java        | 171 +++++++++++++++++++
 .../exception/InvalidJsonException.java            |  26 +++
 .../traffic_control/exception/LoginException.java  |  26 +++
 .../exception/OperationException.java              |  26 +++
 .../exception/TrafficControlException.java         |  26 +++
 traffic_control/clients/java/pom.xml               |  47 ++++++
 traffic_control/clients/java/trafficops/.gitignore |   1 +
 traffic_control/clients/java/trafficops/pom.xml    |  40 +++++
 .../com/comcast/cdn/traffic_control/TOSession.java | 181 +++++++++++++++++++++
 .../comcast/cdn/traffic_control/models/Alert.java  |  20 +++
 .../cdn/traffic_control/models/Response.java       |  27 +++
 .../comcast/cdn/traffic_control/TOSessionTest.java |  85 ++++++++++
 .../trafficops/src/test/resources/logback-test.xml |  12 ++
 16 files changed, 722 insertions(+)

diff --git a/traffic_control/clients/java/.gitignore b/traffic_control/clients/java/.gitignore
new file mode 100644
index 0000000..0ac589d
--- /dev/null
+++ b/traffic_control/clients/java/.gitignore
@@ -0,0 +1,4 @@
+.settings
+.project
+.classpath
+.factorypath
diff --git a/traffic_control/clients/java/common/.gitignore b/traffic_control/clients/java/common/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/traffic_control/clients/java/common/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/traffic_control/clients/java/common/pom.xml b/traffic_control/clients/java/common/pom.xml
new file mode 100644
index 0000000..9c40861
--- /dev/null
+++ b/traffic_control/clients/java/common/pom.xml
@@ -0,0 +1,29 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.comcast.cdn.traffic_control</groupId>
+		<artifactId>traffic-control-java-client</artifactId>
+		<version>0.0.1-SNAPSHOT</version>
+	</parent>
+	<artifactId>traffic-control-java-client-common</artifactId>
+	<dependencies>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpasyncclient</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.auto.value</groupId>
+			<artifactId>auto-value</artifactId>
+			<version>1.5.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/RestApiSession.java b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/RestApiSession.java
new file mode 100644
index 0000000..941d4c4
--- /dev/null
+++ b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/RestApiSession.java
@@ -0,0 +1,171 @@
+package com.comcast.cdn.traffic_control;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.concurrent.FutureCallback;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
+import org.apache.http.message.BasicHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.comcast.cdn.traffic_control.exception.OperationException;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class RestApiSession implements Closeable {
+	@SuppressWarnings("unused")
+	private static final Logger LOG = LoggerFactory.getLogger(RestApiSession.class);
+
+	private static final String URL_FORMAT_STR = "%s://%s:%s/%s/%s/%s";
+	
+	public static final String DEFAULT_API_PATH = "api";
+	public static final String DEFAULT_API_VERSION = "1.2";
+	public static final ImmutableList<Header> DEFAULT_HEADERS;
+	static {
+		DEFAULT_HEADERS = ImmutableList.<Header>builder()
+				.add(new BasicHeader("Content-Type", "application/json; charset=UTF-8")).build();
+	};
+
+	private CloseableHttpAsyncClient httpclient;
+
+	public void open() {
+		if (httpclient == null) {
+			RequestConfig globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT).build();
+			CookieStore cookieStore = new BasicCookieStore();
+			HttpClientContext context = HttpClientContext.create();
+			context.setCookieStore(cookieStore);
+
+			httpclient = HttpAsyncClients.custom()
+					.setDefaultRequestConfig(globalConfig)
+					.setDefaultCookieStore(cookieStore).build();
+		}
+
+		if (!httpclient.isRunning()) {
+			httpclient.start();
+		}
+	}
+
+	public boolean isRunning() {
+		if (httpclient == null) {
+			return false;
+		} else {
+			return httpclient.isRunning();
+		}
+	}
+
+	public void close() throws IOException {
+		if (httpclient != null) {
+			httpclient.close();
+			httpclient = null;
+		}
+	}
+
+	public String buildUrl(String path) {
+		return String.format(URL_FORMAT_STR, this.ssl() ?"https":"http", this.host(), this.port(), this.apiBasePath(),
+				this.apiVersion(), path);
+	}
+	
+	public CompletableFuture<HttpResponse> get(String url) {
+		return execute(RequestBuilder.get(url));
+	}
+	
+	public CompletableFuture<HttpResponse> post(String url, String body) {
+		final HttpEntity e = new StringEntity(body, Charsets.UTF_8);
+		return execute(RequestBuilder.post()
+				.setUri(url)
+				.setEntity(e));
+	}
+	
+	public CompletableFuture<HttpResponse> execute(RequestBuilder request) {
+		for(Header h: this.defaultHeaders()) {
+			request.addHeader(h);
+		}
+		
+		return this.execute(request.build());
+	}
+	
+	private CompletableFuture<HttpResponse> execute(HttpUriRequest request) {
+		final CompletableFutureCallback future = new CompletableFutureCallback();
+		try {
+			this.open();
+			this.httpclient.execute(request, future);
+		} catch(Throwable e) {
+			future.completeExceptionally(e);
+		}
+		
+		return future;
+	}
+	
+	private class CompletableFutureCallback extends CompletableFuture<HttpResponse> implements FutureCallback<HttpResponse>{
+		@Override
+		public void completed(HttpResponse result) {
+			this.complete(result);
+		}
+
+		@Override
+		public void failed(Exception ex) {
+			this.completeExceptionally(ex);
+		}
+
+		@Override
+		public void cancelled() {
+			this.completeExceptionally(new OperationException("HTTP Request was cancelled"));
+		}
+	}
+
+	public abstract String host();
+
+	public abstract int port();
+
+	public abstract String apiVersion();
+
+	public abstract String apiBasePath();
+
+	public abstract ImmutableList<Header> defaultHeaders();
+
+	public abstract boolean ssl();
+
+	static Builder builder() {
+		return new AutoValue_RestApiSession.Builder()
+				.setApiBasePath(DEFAULT_API_PATH)
+				.setApiVersion(DEFAULT_API_VERSION)
+				.setDefaultHeaders(DEFAULT_HEADERS);
+	}
+	
+	public abstract Builder toBuilder();
+
+	@AutoValue.Builder
+	public abstract static class Builder {
+		public abstract RestApiSession build();
+
+		public abstract Builder setHost(String host);
+
+		public abstract Builder setPort(int port);
+
+		public abstract Builder setApiVersion(String version);
+
+		public abstract Builder setApiBasePath(String version);
+
+		public abstract Builder setSsl(boolean ssl);
+
+		public abstract Builder setDefaultHeaders(ImmutableList<Header> headers);
+		public abstract ImmutableList.Builder<Header> defaultHeadersBuilder();
+
+	}
+}
diff --git a/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/InvalidJsonException.java b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/InvalidJsonException.java
new file mode 100644
index 0000000..696574b
--- /dev/null
+++ b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/InvalidJsonException.java
@@ -0,0 +1,26 @@
+package com.comcast.cdn.traffic_control.exception;
+
+public class InvalidJsonException extends TrafficControlException {
+	private static final long serialVersionUID = 1884362711438565843L;
+
+	public InvalidJsonException() {
+		super();
+	}
+
+	public InvalidJsonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+
+	public InvalidJsonException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public InvalidJsonException(String message) {
+		super(message);
+	}
+
+	public InvalidJsonException(Throwable cause) {
+		super(cause);
+	}
+	
+}
diff --git a/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/LoginException.java b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/LoginException.java
new file mode 100644
index 0000000..83fcdc6
--- /dev/null
+++ b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/LoginException.java
@@ -0,0 +1,26 @@
+package com.comcast.cdn.traffic_control.exception;
+
+public class LoginException extends TrafficControlException {
+	private static final long serialVersionUID = -5021620597469977194L;
+
+	public LoginException() {
+		super();
+	}
+
+	public LoginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+
+	public LoginException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public LoginException(String message) {
+		super(message);
+	}
+
+	public LoginException(Throwable cause) {
+		super(cause);
+	}
+	
+}
diff --git a/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/OperationException.java b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/OperationException.java
new file mode 100644
index 0000000..1a019fa
--- /dev/null
+++ b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/OperationException.java
@@ -0,0 +1,26 @@
+package com.comcast.cdn.traffic_control.exception;
+
+public class OperationException extends TrafficControlException {
+	private static final long serialVersionUID = 8799467021892976240L;
+
+	public OperationException() {
+		super();
+	}
+
+	public OperationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+
+	public OperationException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public OperationException(String message) {
+		super(message);
+	}
+
+	public OperationException(Throwable cause) {
+		super(cause);
+	}
+	
+}
diff --git a/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/TrafficControlException.java b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/TrafficControlException.java
new file mode 100644
index 0000000..617db9d
--- /dev/null
+++ b/traffic_control/clients/java/common/src/main/java/com/comcast/cdn/traffic_control/exception/TrafficControlException.java
@@ -0,0 +1,26 @@
+package com.comcast.cdn.traffic_control.exception;
+
+public class TrafficControlException extends Exception {
+	private static final long serialVersionUID = 914940907727369814L;
+
+	public TrafficControlException() {
+		super();
+	}
+
+	public TrafficControlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+
+	public TrafficControlException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public TrafficControlException(String message) {
+		super(message);
+	}
+
+	public TrafficControlException(Throwable cause) {
+		super(cause);
+	}
+	
+}
diff --git a/traffic_control/clients/java/pom.xml b/traffic_control/clients/java/pom.xml
new file mode 100644
index 0000000..c921a8e
--- /dev/null
+++ b/traffic_control/clients/java/pom.xml
@@ -0,0 +1,47 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.comcast.cdn.traffic_control</groupId>
+	<artifactId>traffic-control-java-client</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>pom</packaging>
+	<modules>
+		<module>common</module>
+		<module>trafficops</module>
+	</modules>
+	<properties>
+		<java.version>1.8</java.version>
+	</properties>
+	
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>${java.version}</source>
+					<target>${java.version}</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>org.slf4j</groupId>
+				<artifactId>slf4j-api</artifactId>
+				<version>1.7.25</version>
+			</dependency>
+			<dependency>
+				<groupId>org.apache.httpcomponents</groupId>
+				<artifactId>httpasyncclient</artifactId>
+				<version>4.1.3</version>
+			</dependency>
+			<dependency>
+				<groupId>com.google.guava</groupId>
+				<artifactId>guava</artifactId>
+				<version>23.5-jre</version>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+</project>
\ No newline at end of file
diff --git a/traffic_control/clients/java/trafficops/.gitignore b/traffic_control/clients/java/trafficops/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/traffic_control/clients/java/trafficops/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/traffic_control/clients/java/trafficops/pom.xml b/traffic_control/clients/java/trafficops/pom.xml
new file mode 100644
index 0000000..2c3a5b3
--- /dev/null
+++ b/traffic_control/clients/java/trafficops/pom.xml
@@ -0,0 +1,40 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.comcast.cdn.traffic_control</groupId>
+		<artifactId>traffic-control-java-client</artifactId>
+		<version>0.0.1-SNAPSHOT</version>
+	</parent>
+	<artifactId>traffic-control-java-client-trafficops</artifactId>
+	<dependencies>
+		<dependency>
+			<groupId>com.comcast.cdn.traffic_control</groupId>
+			<artifactId>traffic-control-java-client-common</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+			<version>2.8.2</version>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.12</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+			<version>1.1.3</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-core</artifactId>
+			<version>2.12.0</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/TOSession.java b/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/TOSession.java
new file mode 100644
index 0000000..82791f4
--- /dev/null
+++ b/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/TOSession.java
@@ -0,0 +1,181 @@
+package com.comcast.cdn.traffic_control;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.StringEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.comcast.cdn.traffic_control.exception.LoginException;
+import com.comcast.cdn.traffic_control.exception.OperationException;
+import com.comcast.cdn.traffic_control.models.Alert;
+import com.comcast.cdn.traffic_control.models.Response;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+@AutoValue
+public abstract class TOSession {
+	@SuppressWarnings("unused")
+	private static final Logger LOG = LoggerFactory.getLogger(TOSession.class);
+	
+	private static final Gson gson = new GsonBuilder()
+			.create();
+	
+	private boolean isLoggedIn = false;
+	
+	public String toUrl() {
+		return this.restClient().buildUrl("/");
+	}
+	public boolean isLoggedIn() {
+		return isLoggedIn;
+	}
+	
+	public CompletableFuture<Boolean> login(final String username, final String password) {
+		final String url = this.restClient().buildUrl("user/login.json");
+		
+		return ResponseFuture.builder()
+			.setHandleException((f,t)-> {
+				f.completeExceptionally(new LoginException(String.format("Failed to login with username %s", username), t));
+			})
+			.setMethod(ResponseFuture.Method.POST)
+			.setUrl(url)
+			.setBody(gson.toJson(ImmutableMap.<String,String>of("u", username, "p", password))).build()
+			.thenApply(r->{
+				isLoggedIn = true;
+				return true;
+			});
+	}
+	
+	public CompletableFuture<Response.CollectionResponse> getServers(){
+		final String url = this.restClient().buildUrl("servers.json");
+		return ResponseFuture.builder(Response.CollectionResponse.class)
+				.setMethod(ResponseFuture.Method.GET)
+				.setUrl(url)
+				.setSession(this.restClient())
+				.build();
+	}
+	
+	public CompletableFuture<Response.CollectionResponse> getDeliveryServices(){
+		final String url = this.restClient().buildUrl("deliveryservices.json");
+		LOG.debug("getDeliveryService url {}", url);
+		return ResponseFuture.builder(Response.CollectionResponse.class)
+				.setMethod(ResponseFuture.Method.GET)
+				.setUrl(url)
+				.setSession(this.restClient())
+				.build();
+	}
+	
+	
+	@AutoValue
+	protected abstract static class ResponseFuture<T extends Response> extends CompletableFuture<T> implements BiConsumer<HttpResponse, Throwable> {
+		private static final Logger LOG = LoggerFactory.getLogger(ResponseFuture.class);
+		public static enum Method{
+			POST	, GET
+		}
+		
+		public abstract Optional<BiConsumer<ResponseFuture<T>, Throwable>> handleException();
+		public abstract Class<T> responseType();
+		public abstract Method method();
+		public abstract String url();
+		public abstract RestApiSession session();
+		public abstract Optional<String> body();
+		
+		public static <T extends Response> Builder<T> builder(Class<T> response) {
+			return new AutoValue_TOSession_ResponseFuture.Builder<T>()
+					.setResponseType(response);
+		}
+		public static Builder<Response> builder() {
+			return builder(Response.class);
+		}
+		
+		public ResponseFuture<T> execute(){
+			LOG.debug("Requesting: {} {}", this.method(), this.url());
+			RequestBuilder rBuilder = RequestBuilder.create(this.method().toString());
+			if(this.body().isPresent()) {
+				rBuilder.setEntity(new StringEntity(this.body().get(), Charsets.UTF_8));
+			}
+			this.session().execute(rBuilder).whenComplete(this);
+			return this;
+		}
+		
+		@AutoValue.Builder
+		public abstract static class Builder<T extends Response> {
+			public ResponseFuture<T> build(){
+				return autoBuild().execute();
+			}
+			protected abstract ResponseFuture<T> autoBuild();
+			public abstract Builder<T> setHandleException(BiConsumer<ResponseFuture<T>, Throwable> function);
+			public abstract Builder<T> setResponseType(Class<T> respone);
+			public abstract Builder<T> setMethod(Method method);
+			public abstract Builder<T> setUrl(String url);
+			public abstract Builder<T> setSession(RestApiSession session);
+			public abstract Builder<T> setBody(String body);
+		}
+		
+		@Override
+		public void accept(HttpResponse res, Throwable u) {
+			try {
+				switch(res.getStatusLine().getStatusCode()) {
+					case 200:
+						break;
+					case 401:
+						_handleException(new LoginException("Login required"));
+						return;
+					default:
+						_handleException(new OperationException(String.format("None 200 response: %s %s", res.getStatusLine().getStatusCode(), res.getStatusLine().getReasonPhrase())));
+						return;
+				}
+				
+				InputStreamReader r = new InputStreamReader(res.getEntity().getContent());
+				T resp = gson.fromJson(r, responseType());
+				if(resp.getAlerts() != null) {
+					for(Alert a: resp.getAlerts()) {
+						if("error".equals(a.getLevel())) {
+							_handleException(new OperationException("Recieved error from server: "+ a.getText()));
+							return;
+						}
+					}
+				}
+				
+				this.complete(resp);
+			} catch (UnsupportedOperationException | IOException e) {
+				_handleException(new OperationException("Reading response failed", e));
+				return;
+			}
+		}
+		
+		private void _handleException(Throwable t) {
+			if(handleException().isPresent()) {
+				handleException().get().accept(this, t);
+			}
+			
+			if(!this.isDone()) {
+				this.completeExceptionally(t);
+			}
+		}
+	}
+	
+	public abstract RestApiSession restClient();
+	
+	static Builder builder() {
+		return new AutoValue_TOSession.Builder();
+	}
+	public abstract Builder toBuilder();
+	
+	@AutoValue.Builder
+	public abstract static class Builder {
+		public abstract TOSession build();
+		
+		public abstract Builder setRestClient(RestApiSession restClient);
+		public abstract RestApiSession.Builder restClientBuilder();
+	}
+}
diff --git a/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/models/Alert.java b/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/models/Alert.java
new file mode 100644
index 0000000..0a6e2f9
--- /dev/null
+++ b/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/models/Alert.java
@@ -0,0 +1,20 @@
+package com.comcast.cdn.traffic_control.models;
+
+public class Alert {
+	private String level;
+	private String text;
+	
+	public String getLevel() {
+		return level;
+	}
+	public void setLevel(String level) {
+		this.level = level;
+	}
+	public String getText() {
+		return text;
+	}
+	public void setText(String text) {
+		this.text = text;
+	}
+	
+}
diff --git a/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/models/Response.java b/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/models/Response.java
new file mode 100644
index 0000000..987aee2
--- /dev/null
+++ b/traffic_control/clients/java/trafficops/src/main/java/com/comcast/cdn/traffic_control/models/Response.java
@@ -0,0 +1,27 @@
+package com.comcast.cdn.traffic_control.models;
+
+import java.util.List;
+import java.util.Map;
+
+public class Response {
+	private List<Alert> alerts;
+
+	public List<Alert> getAlerts() {
+		return alerts;
+	}
+	public void setAlerts(List<Alert> alerts) {
+		this.alerts = alerts;
+	}
+	
+	public class CollectionResponse extends Response {
+		private List<Map<String, Object>> response;
+
+		public List<Map<String, Object>> getResponse() {
+			return response;
+		}
+
+		public void setResponse(List<Map<String, Object>> response) {
+			this.response = response;
+		}
+	}
+}
diff --git a/traffic_control/clients/java/trafficops/src/test/java/com/comcast/cdn/traffic_control/TOSessionTest.java b/traffic_control/clients/java/trafficops/src/test/java/com/comcast/cdn/traffic_control/TOSessionTest.java
new file mode 100644
index 0000000..efa310e
--- /dev/null
+++ b/traffic_control/clients/java/trafficops/src/test/java/com/comcast/cdn/traffic_control/TOSessionTest.java
@@ -0,0 +1,85 @@
+package com.comcast.cdn.traffic_control;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.message.BasicStatusLine;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.comcast.cdn.traffic_control.exception.LoginException;
+import com.comcast.cdn.traffic_control.models.Response.CollectionResponse;
+
+public class TOSessionTest {
+	private static final Logger LOG = LoggerFactory.getLogger(TOSessionTest.class);
+	
+	public static final String DeliveryService_Good_Response = "{\"response\": [{\"cachegroup\": \"us-co-denver\"}]}";
+	
+	private RestApiSession sessionMock;
+	
+	@Before
+	public void before() {
+		sessionMock = Mockito.mock(RestApiSession.class, Mockito.CALLS_REAL_METHODS);
+	}
+	@After
+	public void after() {
+		sessionMock=null;
+	}
+
+	@Test
+	public void testBuild() {
+		TOSession.builder().setRestClient(sessionMock).build();
+	}
+	
+	@Test(expected=LoginException.class)
+	public void test401Response() throws Throwable {
+		HttpResponse resp = Mockito.mock(HttpResponse.class);
+		Mockito.when(resp.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_0, 401, "Not Auth"));
+		
+		CompletableFuture<HttpResponse> f = new CompletableFuture<>();
+		f.complete(resp);
+		
+		Mockito.doReturn(f).when(sessionMock).execute(Mockito.any(RequestBuilder.class));
+		
+		TOSession session = TOSession.builder().setRestClient(sessionMock).build();
+		
+		try {
+			session.getDeliveryServices().get();
+		} catch(Throwable e) {
+			throw e.getCause();
+		}
+	}
+	
+	@Test
+	public void deliveryServices() throws Throwable {
+		HttpResponse resp = Mockito.mock(HttpResponse.class);
+		Mockito.doReturn(new BasicStatusLine(HttpVersion.HTTP_1_0, 200, "Ok")).when(resp).getStatusLine();
+		Mockito.doReturn(new StringEntity(DeliveryService_Good_Response)).when(resp).getEntity();
+		
+		CompletableFuture<HttpResponse> f = new CompletableFuture<>();
+		f.complete(resp);
+		
+		Mockito.doReturn(f).when(sessionMock).execute(Mockito.any(RequestBuilder.class));
+		
+		TOSession session = TOSession.builder().setRestClient(sessionMock).build();
+		CollectionResponse cResp = session.getDeliveryServices().get();
+		
+		assertNotNull(cResp);
+		assertNotNull(cResp.getResponse());
+		assertEquals(1, cResp.getResponse().size());
+		
+		final Map<String,Object> service = cResp.getResponse().get(0);
+		assertEquals("us-co-denver", service.get("cachegroup"));
+		LOG.debug("Service: {}", service);
+	}
+}
diff --git a/traffic_control/clients/java/trafficops/src/test/resources/logback-test.xml b/traffic_control/clients/java/trafficops/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..185b678
--- /dev/null
+++ b/traffic_control/clients/java/trafficops/src/test/resources/logback-test.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<configuration>
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss} [%p] [%t] %C:%L %m%n</pattern>
+		</encoder>
+	</appender>
+
+	<root level="DEBUG">
+		<appender-ref ref="STDOUT" />
+	</root>
+</configuration>
\ No newline at end of file