content: /*
   * 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.RpcStatsContext;
  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.
   * 
   * 
   * IMPORTANT:<p>
   * 
   * Code changes to allow synchronised RPC calls:<p>
   * 
   * Changed function {@link com.google.gwt.user.client.rpc.impl.RemoteServiceProxy#doPrepareRequestBuilderImpl(ResponseReader, String, RpcStatsContext, String, AsyncCallback<T>)}<p>
   * Added function {@link com.google.gwt.user.client.rpc.impl.RemoteServiceProxy#isSync(String)}<p>
   */
  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()} &amp;&amp;
     * {@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 ServiceDefTarget#getRpcToken()
     */
    public RpcToken getRpcToken() {
      return rpcToken;
    }
    
    /**
     * @see ServiceDefTarget#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}.
     * @throws RpcTokenException if types mismatch.
     */
    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);
      
      // changed code
      rpcRequestBuilder.setSync(isSync(methodName));
      // changed code
      
      rpcRequestBuilder.setContentType(RPC_CONTENT_TYPE);
      rpcRequestBuilder.setRequestData(requestData);
      rpcRequestBuilder.setRequestId(statsContext.getRequestId());
      return rpcRequestBuilder.finish();
    }
  
    private void ensureRpcRequestBuilder() {
      if (rpcRequestBuilder == null) {
        rpcRequestBuilder = new RpcRequestBuilder();
      }
    }
    
    /**
     * Added function to allow syncronised RPC calls by implementations overriding this function.<p>
     * 
     * @param methodName the method name
     * 
     * @return <code>true</code> to set the method call synchronised
     */
    protected boolean isSync(String methodName) {
        return false;
    }
  }
  