Added an asynchronous client for invoking Web-services. This client uses a queue to hold web-service requests and returns the control back to the caller. A separate thread executes these web-service requests and invokes a caller-suplied callback with the results.

Made WebServiceClient reusable across different type of Web-service calls.
Added configurable logging support in RequestSender, ResponseReceiver and WebServiceClients. Added some logging.
Throwing WebServiceSystemException instead of RuntimException.
This commit is contained in:
Inderjeet Singh 2010-01-20 13:59:59 +00:00
parent 0a134db2f0
commit e4e9254034
10 changed files with 360 additions and 15 deletions

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2010 Google Inc.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package com.google.gson.webservice.client;
import java.util.concurrent.BlockingQueue;
import com.google.gson.webservice.definition.WebServiceCall;
import com.google.gson.webservice.definition.WebServiceResponse;
import com.google.gson.webservice.definition.WebServiceSystemException;
/**
* A consumer that executes in its own thread consuming queue entries and invoking web-service calls
*
* @author inder
*/
final class QueueConsumer implements Runnable {
private final BlockingQueue<QueueEntry> queue;
private WebServiceClient client;
QueueConsumer(BlockingQueue<QueueEntry> queue, WebServiceClient client) {
this.queue = queue;
this.client = client;
}
@Override
public void run() {
try {
while(true) {
consume(queue.take());
}
} catch (InterruptedException e) {
// exit
}
}
private void consume(QueueEntry entry) {
try {
WebServiceResponse response = client.getResponse(entry.callSpec, entry.request);
WebServiceCall call = new WebServiceCall(entry.callSpec, entry.request, response);
entry.responseCallback.handleResponse(call);
} catch (WebServiceSystemException e) {
entry.responseCallback.handleError(e, entry.request, entry.callSpec);
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2010 Google Inc.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package com.google.gson.webservice.client;
import com.google.gson.webservice.definition.WebServiceCallSpec;
import com.google.gson.webservice.definition.WebServiceRequest;
/**
* A holder class for an entry stored in queue. It contains references to the request, callspec,
* and the client-supplied callback to provide sufficient information to execute a web-service call.
*
* @author inder
*/
final class QueueEntry {
final WebServiceCallSpec callSpec;
final WebServiceRequest request;
final ResponseCallback responseCallback;
QueueEntry(WebServiceCallSpec callSpec, WebServiceRequest request,
ResponseCallback responseCallback) {
this.callSpec = callSpec;
this.request = request;
this.responseCallback = responseCallback;
}
}

View File

@ -21,12 +21,15 @@ import java.io.Writer;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gson.Gson;
import com.google.gson.webservice.definition.HeaderMap;
import com.google.gson.webservice.definition.HeaderMapSpec;
import com.google.gson.webservice.definition.RequestBody;
import com.google.gson.webservice.definition.WebServiceRequest;
import com.google.gson.webservice.definition.WebServiceSystemException;
/**
* Class to send Web service requests on a {@link HttpURLConnection}.
@ -35,9 +38,16 @@ import com.google.gson.webservice.definition.WebServiceRequest;
*/
public final class RequestSender {
private final Gson gson;
private final Logger logger;
private final Level logLevel;
public RequestSender(Gson gson) {
this(gson, null);
}
public RequestSender(Gson gson, Level logLevel) {
this.gson = gson;
logger = logLevel == null ? null : Logger.getLogger(RequestSender.class.getName());
this.logLevel = logLevel;
}
public void send(HttpURLConnection conn, WebServiceRequest request) {
@ -60,7 +70,7 @@ public final class RequestSender {
// Initiate the sending of the request.
conn.connect();
} catch (IOException e) {
throw new RuntimeException(e);
throw new WebServiceSystemException(e);
}
}
@ -72,6 +82,9 @@ public final class RequestSender {
Object value = entry.getValue();
String json = gson.toJson(value, type);
conn.addRequestProperty(paramName, json);
if (logger != null) {
logger.log(logLevel, String.format("Request param: %s:%s", paramName, json));
}
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2010 Google Inc.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package com.google.gson.webservice.client;
import com.google.gson.webservice.definition.WebServiceCall;
import com.google.gson.webservice.definition.WebServiceCallSpec;
import com.google.gson.webservice.definition.WebServiceRequest;
import com.google.gson.webservice.definition.WebServiceSystemException;
/**
* A client-supplied callback to be used with {@link WebServiceClientAsync}. When a web-service
* call is executed asynchronously, this callback is invoked with the results.
*
* @author inder
*/
public interface ResponseCallback {
public void handleResponse(WebServiceCall call);
public void handleError(WebServiceSystemException e, WebServiceRequest request,
WebServiceCallSpec callSpec);
}

View File

@ -22,6 +22,8 @@ import java.io.Reader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gson.Gson;
import com.google.gson.webservice.definition.HeaderMap;
@ -39,10 +41,17 @@ import com.google.gson.webservice.definition.WebServiceResponse;
public final class ResponseReceiver {
private final Gson gson;
private final ResponseSpec spec;
private final Logger logger;
private final Level logLevel;
public ResponseReceiver(Gson gson, ResponseSpec spec) {
this(gson, spec, null);
}
public ResponseReceiver(Gson gson, ResponseSpec spec, Level logLevel) {
this.gson = gson;
this.spec = spec;
this.logger = logLevel == null ? null : Logger.getLogger(ResponseReceiver.class.getName());
this.logLevel = logLevel;
}
public WebServiceResponse receive(HttpURLConnection conn) {
@ -64,6 +73,9 @@ public final class ResponseReceiver {
String paramName = entry.getKey();
String json = conn.getHeaderField(paramName);
if (json != null) {
if (logger != null) {
logger.log(logLevel, String.format("Response Header: %s:%s\n", paramName, json));
}
Type typeOfT = paramsSpec.getTypeFor(paramName);
Object value = gson.fromJson(json, typeOfT);
paramsBuilder.put(paramName, value, typeOfT);

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2010 Google Inc.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package com.google.gson.webservice.client;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* An executor that uses a single thread to execute all calls
*
* @author inder
*/
final class SingleThreadExecutor implements TaskExecutor {
private ExecutorService executor;
public void execute(final Runnable r) {
executor = Executors.newSingleThreadExecutor();
executor.execute(r);
}
@Override
public void shutdownNow() {
executor.shutdownNow();
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2010 Google Inc.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package com.google.gson.webservice.client;
import java.util.concurrent.Executor;
/**
* An {@link Executor} with an additional method for shutdown. We could have just used
* {@link java.util.concurent.ExecutorService}, however, that requires too many methods to be
* implemented.
*
* @author inder
*/
interface TaskExecutor extends Executor {
public void shutdownNow();
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2010 Google Inc.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package com.google.gson.webservice.client;
/**
* An executor that uses a new thread to execute each call
*
* @author inder
*/
final class ThreadPerTaskExecutor implements TaskExecutor {
private Thread thread;
public void execute(final Runnable r) {
thread = new Thread(r);
thread.start();
}
@Override
public void shutdownNow() {
if (thread != null) {
thread.interrupt();
}
}
}

View File

@ -19,11 +19,17 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.webservice.definition.ResponseBody;
import com.google.gson.webservice.definition.WebServiceCallSpec;
import com.google.gson.webservice.definition.WebServiceRequest;
import com.google.gson.webservice.definition.WebServiceResponse;
import com.google.gson.webservice.definition.WebServiceSystemException;
import com.google.gson.webservice.typeadapters.ResponseBodyGsonConverter;
/**
* Main class used by clients to access a Gson Web service.
@ -32,16 +38,20 @@ import com.google.gson.webservice.definition.WebServiceResponse;
*/
public final class WebServiceClient {
private final WebServiceConfig config;
private final WebServiceCallSpec callSpec;
private final Gson gson;
private final Logger logger;
private final Level logLevel;
public WebServiceClient(Gson gson, WebServiceConfig serverConfig, WebServiceCallSpec callSpec) {
this.gson = gson;
public WebServiceClient(WebServiceConfig serverConfig) {
this(serverConfig, null);
}
public WebServiceClient(WebServiceConfig serverConfig, Level logLevel) {
this.config = serverConfig;
this.callSpec = callSpec;
this.logger = logLevel == null ? null : Logger.getLogger(WebServiceClient.class.getName());
this.logLevel = logLevel;
}
private URL getWebServiceUrl() {
private URL getWebServiceUrl(WebServiceCallSpec callSpec) {
String url = config.getServiceBaseUrl() + callSpec.getPath().get();
try {
return new URL(url);
@ -50,23 +60,31 @@ public final class WebServiceClient {
}
}
public WebServiceResponse getResponse(WebServiceRequest request) {
public WebServiceResponse getResponse(WebServiceCallSpec callSpec, WebServiceRequest request) {
try {
HttpURLConnection conn = (HttpURLConnection) getWebServiceUrl().openConnection();
URL webServiceUrl = getWebServiceUrl(callSpec);
if (logger != null) {
logger.log(logLevel, "Opening connection to " + webServiceUrl);
}
HttpURLConnection conn = (HttpURLConnection) webServiceUrl.openConnection();
Gson gson = new GsonBuilder()
.registerTypeAdapter(ResponseBody.class,
new ResponseBodyGsonConverter(callSpec.getResponseSpec().getBodySpec()))
.create();
RequestSender requestSender = new RequestSender(gson);
requestSender.send(conn, request);
ResponseReceiver responseReceiver = new ResponseReceiver(gson, callSpec.getResponseSpec());
ResponseReceiver responseReceiver =
new ResponseReceiver(gson, callSpec.getResponseSpec(), logLevel);
return responseReceiver.receive(conn);
} catch (IOException e) {
throw new RuntimeException(e);
throw new WebServiceSystemException(e);
} catch (IllegalArgumentException e) {
throw new WebServiceSystemException(e);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{config:").append(config).append(",callSpec:").append(callSpec);
sb.append("gson:").append(gson).append("}");
return sb.toString();
return String.format("config:%s", config);
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2010 Google Inc.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
package com.google.gson.webservice.client;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import com.google.gson.webservice.definition.WebServiceCallSpec;
import com.google.gson.webservice.definition.WebServiceRequest;
import com.google.gson.webservice.definition.WebServiceSystemException;
/**
* A client for invoking a JSON-based Web-service in an asynchronous manner. The call is queued,
* and control returns to the caller. A separate thread executes the call, and invokes the
* client-supplied callback with results.
*
* @author inder
*/
public final class WebServiceClientAsync {
private final BlockingQueue<QueueEntry> queue;
private final boolean threadPerTask;
private final TaskExecutor executor;
public WebServiceClientAsync(WebServiceConfig serverConfig) {
this(serverConfig, null);
}
public WebServiceClientAsync(WebServiceConfig serverConfig, Level logLevel) {
this(new WebServiceClient(serverConfig, logLevel));
}
public WebServiceClientAsync(WebServiceClient client) {
queue = new LinkedBlockingQueue<QueueEntry>();
this.threadPerTask = true;
QueueConsumer consumer = new QueueConsumer(queue, client);
executor = getExecutor();
executor.execute(consumer);
}
private TaskExecutor getExecutor() {
return threadPerTask ? new ThreadPerTaskExecutor() : new SingleThreadExecutor();
}
public void callAsync(WebServiceCallSpec callSpec, WebServiceRequest request,
ResponseCallback responseCallback) {
try {
queue.put(new QueueEntry(callSpec, request, responseCallback));
} catch (InterruptedException e) {
throw new WebServiceSystemException(e);
}
}
public void shutdownNow() {
executor.shutdownNow();
}
}