blob: d2efbfc37e5acb5bdc4dad90e4a34808d5454ac4 [file] [log] [blame]
/*
* Copyright 2008 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.gwt.user.client.rpc.impl;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.HasRpcToken;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.rpc.RpcRequestBuilder;
import com.google.gwt.user.client.rpc.RpcToken;
import com.google.gwt.user.client.rpc.RpcTokenExceptionHandler;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamFactory;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
/**
* Superclass for client-side
* {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} proxies.
*
* For internal use only.
*/
public abstract class RemoteServiceProxy implements SerializationStreamFactory,
ServiceDefTarget, HasRpcToken {
/**
* The content type to be used in HTTP requests.
*/
private static final String RPC_CONTENT_TYPE = "text/x-gwt-rpc; charset=utf-8";
/**
* A helper class that prepares the service to serialize data.
*/
public class ServiceHelper {
private final String fullServiceName;
private final String methodName;
private final RpcStatsContext statsContext;
private SerializationStreamWriter streamWriter;
public ServiceHelper(String serviceName, String methodName) {
this.fullServiceName = serviceName + "." + methodName;
this.methodName = methodName;
this.statsContext = new RpcStatsContext();
}
/**
* Finishes the serialization.
*/
public Request finish(AsyncCallback callback, ResponseReader responseHeader)
throws SerializationException {
String payload = streamWriter.toString();
boolean toss = statsContext.isStatsAvailable()
&& statsContext.stats(statsContext.timeStat(fullServiceName, "requestSerialized"));
return doInvoke(responseHeader, fullServiceName, statsContext, payload, callback);
}
/**
* Finishes the serialization and return a RequestBuilder.
*/
public RequestBuilder finishForRequestBuilder(AsyncCallback callback,
ResponseReader responseHeader) throws SerializationException {
String payload = streamWriter.toString();
boolean toss = statsContext.isStatsAvailable()
&& statsContext.stats(statsContext.timeStat(fullServiceName, "requestSerialized"));
return doPrepareRequestBuilder(
responseHeader, fullServiceName, statsContext, payload, callback);
}
/**
* Starts the serialization.
*/
public SerializationStreamWriter start(String remoteServiceInterfaceName,
int paramCount) throws SerializationException {
boolean toss = statsContext.isStatsAvailable()
&& statsContext.stats(statsContext.timeStat(fullServiceName, "begin"));
streamWriter = createStreamWriter();
if (getRpcToken() != null) {
streamWriter.writeObject(getRpcToken());
}
streamWriter.writeString(remoteServiceInterfaceName);
streamWriter.writeString(methodName);
streamWriter.writeInt(paramCount);
return streamWriter;
}
}
/**
* @deprecated use {@link RpcStatsContext}.
*/
@Deprecated
public static JavaScriptObject bytesStat(String method, int count,
int bytes, String eventType) {
return new RpcStatsContext(count).bytesStat(method, bytes, eventType);
}
/**
* Indicates if RPC statistics should be gathered.
*
* @deprecated use {@link RpcStatsContext}.
*/
@Deprecated
public static boolean isStatsAvailable() {
return new RpcStatsContext(0).isStatsAvailable();
}
/**
* Always use this as {@link #isStatsAvailable()} &&
* {@link #stats(JavaScriptObject)}.
*
* @deprecated use {@link RpcStatsContext}.
*/
@Deprecated
public static boolean stats(JavaScriptObject data) {
return new RpcStatsContext(0).stats(data);
}
/**
* @deprecated use {@link RpcStatsContext}.
*/
@Deprecated
public static JavaScriptObject timeStat(String method, int count,
String eventType) {
return new RpcStatsContext(count).timeStat(method, eventType);
}
/**
* @deprected use {@link RpcStatsContext}.
*/
@Deprecated
protected static int getNextRequestId() {
return RpcStatsContext.getNextRequestId();
}
/**
* @deprecated Use {@link RpcRequestBuilder} instead.
*/
@Deprecated
protected static int getRequestId() {
return RpcStatsContext.getLastRequestId();
}
/**
* Return <code>true</code> if the encoded response contains a value returned
* by the method invocation.
*
* @param encodedResponse
* @return <code>true</code> if the encoded response contains a value returned
* by the method invocation
*/
static boolean isReturnValue(String encodedResponse) {
return encodedResponse.startsWith("//OK");
}
/**
* Return <code>true</code> if the encoded response contains a checked
* exception that was thrown by the method invocation.
*
* @param encodedResponse
* @return <code>true</code> if the encoded response contains a checked
* exception that was thrown by the method invocation
*/
static boolean isThrownException(String encodedResponse) {
return encodedResponse.startsWith("//EX");
}
/**
* Returns a string that encodes the result of a method invocation.
* Effectively, this just removes any headers from the encoded response.
*
* @param encodedResponse
* @return string that encodes the result of a method invocation
*/
private static String getEncodedInstance(String encodedResponse) {
if (isReturnValue(encodedResponse) || isThrownException(encodedResponse)) {
return encodedResponse.substring(4);
}
return encodedResponse;
}
/**
* The module base URL as specified during construction.
*/
private final String moduleBaseURL;
/**
* URL of the {@link com.google.gwt.user.client.rpc.RemoteService
* RemoteService}.
*/
private String remoteServiceURL;
private RpcRequestBuilder rpcRequestBuilder;
private RpcToken rpcToken;
private RpcTokenExceptionHandler rpcTokenExceptionHandler;
/**
* The name of the serialization policy file specified during construction.
*/
private final String serializationPolicyName;
/**
* The {@link Serializer} instance used to serialize and deserialize
* instances.
*/
private final Serializer serializer;
protected RemoteServiceProxy(String moduleBaseURL,
String remoteServiceRelativePath, String serializationPolicyName,
Serializer serializer) {
this.moduleBaseURL = moduleBaseURL;
if (remoteServiceRelativePath != null) {
/*
* If the module relative URL is not null we set the remote service URL to
* be the module base URL plus the module relative remote service URL.
* Otherwise an explicit call to
* ServiceDefTarget.setServiceEntryPoint(String) is required.
*/
this.remoteServiceURL = moduleBaseURL + remoteServiceRelativePath;
}
this.serializer = serializer;
this.serializationPolicyName = serializationPolicyName;
}
/**
* Returns a {@link com.google.gwt.user.client.rpc.SerializationStreamReader
* SerializationStreamReader} that is ready for reading.
*
* @param encoded string that encodes the response of an RPC request
* @return {@link com.google.gwt.user.client.rpc.SerializationStreamReader
* SerializationStreamReader} that is ready for reading
* @throws SerializationException
*/
public SerializationStreamReader createStreamReader(String encoded)
throws SerializationException {
ClientSerializationStreamReader clientSerializationStreamReader = new ClientSerializationStreamReader(
serializer);
clientSerializationStreamReader.prepareToRead(getEncodedInstance(encoded));
return clientSerializationStreamReader;
}
/**
* Returns a {@link com.google.gwt.user.client.rpc.SerializationStreamWriter
* SerializationStreamWriter} that has had
* {@link ClientSerializationStreamWriter#prepareToWrite()} called on it and
* it has already had had the name of the remote service interface written as
* well.
*
* @return {@link com.google.gwt.user.client.rpc.SerializationStreamWriter
* SerializationStreamWriter} that has had
* {@link ClientSerializationStreamWriter#prepareToWrite()} called on
* it and it has already had had the name of the remote service
* interface written as well
*/
public SerializationStreamWriter createStreamWriter() {
ClientSerializationStreamWriter clientSerializationStreamWriter = new ClientSerializationStreamWriter(
serializer, moduleBaseURL, serializationPolicyName);
clientSerializationStreamWriter.prepareToWrite();
return clientSerializationStreamWriter;
}
/**
* @see HasRpcToken#getRpcToken()
*/
public RpcToken getRpcToken() {
return rpcToken;
}
/**
* @see HasRpcToken#getRpcTokenExceptionHandler()
*/
public RpcTokenExceptionHandler getRpcTokenExceptionHandler() {
return rpcTokenExceptionHandler;
}
public String getSerializationPolicyName() {
return serializationPolicyName;
}
/**
* @see ServiceDefTarget#getServiceEntryPoint()
*/
public String getServiceEntryPoint() {
return remoteServiceURL;
}
public void setRpcRequestBuilder(RpcRequestBuilder builder) {
this.rpcRequestBuilder = builder;
}
/**
* @see HasRpcToken#setRpcToken(RpcToken)
*/
public void setRpcToken(RpcToken token) {
checkRpcTokenType(token);
this.rpcToken = token;
}
/**
* @see HasRpcToken#setRpcTokenExceptionHandler(RpcTokenExceptionHandler)
*/
public void setRpcTokenExceptionHandler(RpcTokenExceptionHandler handler) {
this.rpcTokenExceptionHandler = handler;
}
/**
* @see ServiceDefTarget#setServiceEntryPoint(String)
*/
public void setServiceEntryPoint(String url) {
this.remoteServiceURL = url;
}
/**
* This method is overridden by generated proxy classes to ensure that
* current service's {@link RpcToken} is of the type specified in {@link
* RpcToken.RpcTokenImplementation} annotation.
*
* @param token currently set {@link RpcToken}.
*/
protected void checkRpcTokenType(RpcToken token) {
}
protected <T> RequestCallback doCreateRequestCallback(
ResponseReader responseReader, String methodName, RpcStatsContext statsContext,
AsyncCallback<T> callback) {
return new RequestCallbackAdapter<T>(this, methodName, statsContext,
callback, getRpcTokenExceptionHandler(), responseReader);
}
/**
* Performs a remote service method invocation. This method is called by
* generated proxy classes.
*
* @param <T> return type for the AsyncCallback
* @param responseReader instance used to read the return value of the
* invocation
* @param requestData payload that encodes the addressing and arguments of the
* RPC call
* @param callback callback handler
*
* @return a {@link Request} object that can be used to track the request
*/
protected <T> Request doInvoke(ResponseReader responseReader,
String methodName, RpcStatsContext statsContext, String requestData,
AsyncCallback<T> callback) {
RequestBuilder rb = doPrepareRequestBuilderImpl(responseReader, methodName,
statsContext, requestData, callback);
try {
return rb.send();
} catch (RequestException ex) {
InvocationException iex = new InvocationException(
"Unable to initiate the asynchronous service invocation (" +
methodName + ") -- check the network connection",
ex);
callback.onFailure(iex);
} finally {
if (statsContext.isStatsAvailable()) {
statsContext.stats(statsContext.bytesStat(methodName,
requestData.length(), "requestSent"));
}
}
return null;
}
/**
* Configures a RequestBuilder to send an RPC request when the RequestBuilder
* is intended to be returned through the asynchronous proxy interface.
*
* @param <T> return type for the AsyncCallback
* @param responseReader instance used to read the return value of the
* invocation
* @param requestData payload that encodes the addressing and arguments of the
* RPC call
* @param callback callback handler
*
* @return a RequestBuilder object that is ready to have its
* {@link RequestBuilder#send()} method invoked.
*/
protected <T> RequestBuilder doPrepareRequestBuilder(
ResponseReader responseReader, String methodName, RpcStatsContext statsContext,
String requestData, AsyncCallback<T> callback) {
RequestBuilder rb = doPrepareRequestBuilderImpl(responseReader, methodName,
statsContext, requestData, callback);
return rb;
}
/**
* Configures a RequestBuilder to send an RPC request.
*
* @param <T> return type for the AsyncCallback
* @param responseReader instance used to read the return value of the
* invocation
* @param requestData payload that encodes the addressing and arguments of the
* RPC call
* @param callback callback handler
*
* @return a RequestBuilder object that is ready to have its
* {@link RequestBuilder#send()} method invoked.
*/
private <T> RequestBuilder doPrepareRequestBuilderImpl(
ResponseReader responseReader, String methodName, RpcStatsContext statsContext,
String requestData, AsyncCallback<T> callback) {
if (getServiceEntryPoint() == null) {
throw new NoServiceEntryPointSpecifiedException();
}
RequestCallback responseHandler = doCreateRequestCallback(responseReader,
methodName, statsContext, callback);
ensureRpcRequestBuilder();
rpcRequestBuilder.create(getServiceEntryPoint());
rpcRequestBuilder.setCallback(responseHandler);
rpcRequestBuilder.setContentType(RPC_CONTENT_TYPE);
rpcRequestBuilder.setRequestData(requestData);
rpcRequestBuilder.setRequestId(statsContext.getRequestId());
return rpcRequestBuilder.finish();
}
private void ensureRpcRequestBuilder() {
if (rpcRequestBuilder == null) {
rpcRequestBuilder = new RpcRequestBuilder();
}
}
}