blob: d05eaec8f900284ce64370fe3b857e389619d510 [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.GWT;
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.RequestException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamFactory;
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 {
/**
* A global id to track any given request.
*/
private static int requestId;
public static native JavaScriptObject bytesStat(String method, int count,
int bytes) /*-{
var stat = @com.google.gwt.user.client.rpc.impl.RemoteServiceProxy::timeStat(Ljava/lang/String;I)(method, count);
stat.bytes = bytes;
return stat;
}-*/;
/**
* Indicates if RPC statistics should be gathered.
*/
public static boolean isStatsAvailable() {
return GWT.isScript() && isStatsAvailable0();
}
/**
* Always use this as {@link #isStatsAvailable()} &&
* {@link #stats(String, String, int)}.
*/
public static native boolean stats(String invocation, JavaScriptObject data) /*-{
return $stats(@com.google.gwt.core.client.GWT::getModuleName()(), 'rpc',
invocation, data);
}-*/;
public static native JavaScriptObject timeStat(String method, int count) /*-{
return {
id: count,
method: method,
millis: (new Date()).getTime()
};
}-*/;
protected static int getNextRequestId() {
return requestId++;
}
protected static int getRequestId() {
return requestId;
}
/**
* 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;
}
/**
* Indicates if RPC statistics should be gathered.
*/
private static native boolean isStatsAvailable0() /*-{
return @com.google.gwt.core.client.GWT::isScript()() && !!$stats;
}-*/;
/**
* 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;
/**
* 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 ClientSerializationStreamReader 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 ClientSerializationStreamWriter createStreamWriter() {
ClientSerializationStreamWriter clientSerializationStreamWriter = new ClientSerializationStreamWriter(
serializer, moduleBaseURL, serializationPolicyName);
clientSerializationStreamWriter.prepareToWrite();
return clientSerializationStreamWriter;
}
/**
* @see ServiceDefTarget#getServiceEntryPoint()
*/
public String getServiceEntryPoint() {
return remoteServiceURL;
}
/**
* @see ServiceDefTarget#setServiceEntryPoint(String)
*/
public void setServiceEntryPoint(String url) {
this.remoteServiceURL = url;
}
/**
* Performs a remote service method invocation.
*
* @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
*/
@SuppressWarnings("unused")
protected <T> Request doInvoke(ResponseReader responseReader,
String methodName, int invocationCount, String requestData,
AsyncCallback<T> callback) {
if (getServiceEntryPoint() == null) {
throw new NoServiceEntryPointSpecifiedException();
}
RequestCallbackAdapter<T> responseHandler = new RequestCallbackAdapter<T>(
this, methodName, invocationCount, callback, responseReader);
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,
getServiceEntryPoint());
rb.setHeader("Content-Type", "text/x-gwt-rpc; charset=utf-8");
try {
return rb.sendRequest(requestData, responseHandler);
} catch (RequestException ex) {
InvocationException iex = new InvocationException(
"Unable to initiate the asynchronous service invocation -- check the network connection",
ex);
callback.onFailure(iex);
} finally {
boolean toss = isStatsAvailable()
&& stats(methodName + ":" + invocationCount + ":requestSent",
bytesStat(methodName, invocationCount, requestData.length()));
}
return null;
}
}