Package com.google.appengine.tools.development

Source Code of com.google.appengine.tools.development.ApiProxyLocalImpl$PrivilegedApiAction

// Copyright 2008 Google Inc. All Rights Reserved.

package com.google.appengine.tools.development;

import com.google.appengine.api.capabilities.CapabilityStatus;
import com.google.appengine.tools.development.LocalRpcService.Status;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.CallNotFoundException;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.apphosting.api.ApiProxy.LogRecord;
import com.google.apphosting.api.ApiProxy.RequestTooLargeException;
import com.google.apphosting.api.ApiProxy.UnknownException;
import com.google.io.protocol.ProtocolMessage;
import com.google.protobuf.Message;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Implements ApiProxy.Delegate such that the requests are dispatched to local service
* implementations. Used for both the {@link com.google.appengine.tools.development.DevAppServer}
* and for unit testing services.
*
*/
class ApiProxyLocalImpl implements ApiProxyLocal, DevServices {

  private static final Class<?> BYTE_ARRAY_CLASS = byte[].class;

  /**
   * The maximum size of any given API request.
   */
  private static final int MAX_API_REQUEST_SIZE = 1048576;

  private static final String API_DEADLINE_KEY =
      "com.google.apphosting.api.ApiProxy.api_deadline_key";

  static final String IS_OFFLINE_REQUEST_KEY = "com.google.appengine.request.offline";

  /**
   * Implementation of the {@link LocalServiceContext} interface
   */
  private class LocalServiceContextImpl implements LocalServiceContext {

    /**
     * The local server environment
     */
    private final LocalServerEnvironment localServerEnvironment;

    private final LocalCapabilitiesEnvironment localCapabilitiesEnvironment =
        new LocalCapabilitiesEnvironment(System.getProperties());

    /**
     * Creates a new context, for the given application.
     *
     * @param localServerEnvironment The environment for the local server.
     */
    public LocalServiceContextImpl(LocalServerEnvironment localServerEnvironment) {
      this.localServerEnvironment = localServerEnvironment;
    }

    @Override
    public LocalServerEnvironment getLocalServerEnvironment() {
      return localServerEnvironment;
    }

    @Override
    public LocalCapabilitiesEnvironment getLocalCapabilitiesEnvironment() {
      return localCapabilitiesEnvironment;
    }

    @Override
    public Clock getClock() {
      return clock;
    }

    @Override
    public LocalRpcService getLocalService(String packageName) {
      return ApiProxyLocalImpl.this.getService(packageName);
    }
  }

  private static final Logger logger = Logger.getLogger(ApiProxyLocalImpl.class.getName());

  private final Map<String, LocalRpcService> serviceCache =
      new ConcurrentHashMap<String, LocalRpcService>();

  private final Map<String, Method> methodCache = new ConcurrentHashMap<String, Method>();
  final Map<Method, LatencySimulator> latencySimulatorCache =
      new ConcurrentHashMap<Method, LatencySimulator>();

  private final Map<String, String> properties = new HashMap<String, String>();

  private final ExecutorService apiExecutor = Executors.newCachedThreadPool(
      new DaemonThreadFactory(Executors.defaultThreadFactory()));

  private final LocalServiceContext context;

  private Clock clock = Clock.DEFAULT;

  /**
   * Creates the local proxy in a given context
   *
   * @param environment the local server environment.
   */
  protected ApiProxyLocalImpl(LocalServerEnvironment environment) {
    this.context = new LocalServiceContextImpl(environment);
  }

  @Override
  public void log(Environment environment, LogRecord record) {
    logger.log(toJavaLevel(record.getLevel()), record.getMessage());
  }

  @Override
  public void flushLogs(Environment environment) {
    System.err.flush();
  }

  @Override
  public byte[] makeSyncCall(ApiProxy.Environment environment, String packageName,
      String methodName, byte[] requestBytes) {
    ApiProxy.ApiConfig apiConfig = null;
    Double deadline = (Double) environment.getAttributes().get(API_DEADLINE_KEY);
    if (deadline != null) {
      apiConfig = new ApiProxy.ApiConfig();
      apiConfig.setDeadlineInSeconds(deadline);
    }

    Future<byte[]> future =
        makeAsyncCall(environment, packageName, methodName, requestBytes, apiConfig);
    try {
      return future.get();
    } catch (InterruptedException ex) {
      throw new ApiProxy.CancelledException(packageName, methodName);
    } catch (CancellationException ex) {
      throw new ApiProxy.CancelledException(packageName, methodName);
    } catch (ExecutionException ex) {
      if (ex.getCause() instanceof RuntimeException) {
        throw (RuntimeException) ex.getCause();
      } else if (ex.getCause() instanceof Error) {
        throw (Error) ex.getCause();
      } else {
        throw new ApiProxy.UnknownException(packageName, methodName, ex.getCause());
      }
    }
  }

  @Override
  public Future<byte[]> makeAsyncCall(Environment environment, final String packageName,
      final String methodName, byte[] requestBytes, ApiProxy.ApiConfig apiConfig) {
    Semaphore semaphore = (Semaphore) environment.getAttributes().get(
        LocalEnvironment.API_CALL_SEMAPHORE);
    if (semaphore != null) {
      try {
        semaphore.acquire();
      } catch (InterruptedException ex) {
        throw new RuntimeException("Interrupted while waiting on semaphore:", ex);
      }
    }
    boolean offline = environment.getAttributes().get(IS_OFFLINE_REQUEST_KEY) != null;
    AsyncApiCall asyncApiCall = new AsyncApiCall(
        environment, packageName, methodName, requestBytes, semaphore);
    boolean success = false;
    try {
      Callable<byte[]> callable = Executors.privilegedCallable(asyncApiCall);

      Future<byte[]> resultFuture = AccessController.doPrivileged(
          new PrivilegedApiAction(callable, asyncApiCall));
      success = true;
      if (context.getLocalServerEnvironment().enforceApiDeadlines()) {
        long deadlineMillis = (long) (1000.0 * resolveDeadline(packageName, apiConfig, offline));
        resultFuture = new TimedFuture<byte[]>(resultFuture, deadlineMillis, clock) {
          @Override
          protected RuntimeException createDeadlineException() {
            return new ApiProxy.ApiDeadlineExceededException(packageName, methodName);
          }
        };
      }
      return resultFuture;
    } finally {
      if (!success) {
        asyncApiCall.tryReleaseSemaphore();
      }
    }
  }

  @Override
  public List<Thread> getRequestThreads(Environment environment) {
    return Arrays.asList(new Thread[]{Thread.currentThread()});
  }

  private double resolveDeadline(String packageName, ApiProxy.ApiConfig apiConfig,
      boolean isOffline) {
    LocalRpcService service = getService(packageName);
    Double deadline = null;
    if (apiConfig != null) {
      deadline = apiConfig.getDeadlineInSeconds();
    }
    if (deadline == null && service != null) {
      deadline = service.getDefaultDeadline(isOffline);
    }
    if (deadline == null) {
      deadline = 5.0;
    }

    Double maxDeadline = null;
    if (service != null) {
      maxDeadline = service.getMaximumDeadline(isOffline);
    }
    if (maxDeadline == null) {
      maxDeadline = 10.0;
    }
    return Math.min(deadline, maxDeadline);
  }

  private class PrivilegedApiAction implements PrivilegedAction<Future<byte[]>> {

    private final Callable<byte[]> callable;
    private final AsyncApiCall asyncApiCall;

    PrivilegedApiAction(Callable<byte[]> callable, AsyncApiCall asyncApiCall) {
      this.callable = callable;
      this.asyncApiCall = asyncApiCall;
    }

    @Override
    public Future<byte[]> run() {
      final Future<byte[]> result = apiExecutor.submit(callable);
      return new Future<byte[]>() {
        @Override
        public boolean cancel(final boolean mayInterruptIfRunning) {
          return AccessController.doPrivileged(
              new PrivilegedAction<Boolean>() {
                @Override
                public Boolean run() {
                  asyncApiCall.tryReleaseSemaphore();
                  return result.cancel(mayInterruptIfRunning);
                }
              });
        }

        @Override
        public boolean isCancelled() {
          return result.isCancelled();
        }

        @Override
        public boolean isDone() {
          return result.isDone();
        }

        @Override
        public byte[] get() throws InterruptedException, ExecutionException {
          return result.get();
        }

        @Override
        public byte[] get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
          return result.get(timeout, unit);
        }
      };
    }
  }

  /**
   * Convert the specified byte array to a protocol buffer representation of the specified type.
   * This type can either be a subclass of {@link ProtocolMessage} (a legacy protocol buffer
   * implementation), or {@link Message} (the open-sourced protocol buffer implementation).
   */
  private <T> T convertBytesToPb(byte[] bytes, Class<T> requestClass)
      throws IllegalAccessException, InstantiationException,
      InvocationTargetException, NoSuchMethodException {
    if (ProtocolMessage.class.isAssignableFrom(requestClass)) {
      ProtocolMessage<?> proto = (ProtocolMessage<?>) requestClass.newInstance();
      proto.mergeFrom(bytes);
      return requestClass.cast(proto);
    }
    if (Message.class.isAssignableFrom(requestClass)) {
      Method method = requestClass.getMethod("parseFrom", BYTE_ARRAY_CLASS);
      return requestClass.cast(method.invoke(null, bytes));
    }
    throw new UnsupportedOperationException(String.format("Cannot assign %s to either %s or %s",
        classDescription(requestClass), ProtocolMessage.class, Message.class));
  }

  /**
   * Convert the protocol buffer representation to a byte array.  The object can either be an
   * instance of {@link ProtocolMessage} (a legacy protocol buffer implementation), or {@link
   * Message} (the open-sourced protocol buffer implementation).
   */
  private byte[] convertPbToBytes(Object object) {
    if (object instanceof ProtocolMessage) {
      return ((ProtocolMessage<?>) object).toByteArray();
    }
    if (object instanceof Message) {
      return ((Message) object).toByteArray();
    }
    throw new UnsupportedOperationException(String.format("%s is neither %s nor %s",
        classDescription(object.getClass()), ProtocolMessage.class, Message.class));
  }

  /**
   * Create a textual description of a class that is appropriate for
   * troubleshooting problems with {@link #convertBytesToPb(byte[], Class)}
   * or {@link #convertPbToBytes(Object)}.
   *
   * @param klass The class to create a description for.
   * @return A string description.
   */
  private static String classDescription(Class<?> klass) {
    return String.format("(%s extends %s loaded from %s)",
        klass, klass.getSuperclass(),
        klass.getProtectionDomain().getCodeSource().getLocation());
  }

  @Override
  public void setProperty(String property, String value) {
    properties.put(property, value);
  }

  /**
   * Resets the service properties to {@code properties}.
   *
   * @param properties a maybe {@code null} set of properties for local services.
   */
  @Override
  public void setProperties(Map<String, String> properties) {
    this.properties.clear();
    if (properties != null) {
      this.appendProperties(properties);
    }
  }

  /**
   * Appends the given service properties to {@code properties}.
   *
   * @param properties a set of properties to append for local services.
   */
  @Override
  public void appendProperties(Map<String, String> properties) {
    this.properties.putAll(properties);
  }

  /**
   * Stops all services started by this ApiProxy and releases all of its resources.
   */
  @Override
  public void stop() {
    for (LocalRpcService service : serviceCache.values()) {
      service.stop();
    }

    serviceCache.clear();
    methodCache.clear();
    latencySimulatorCache.clear();

  }

  int getMaxApiRequestSize(LocalRpcService rpcService) {
    Integer size = rpcService.getMaxApiRequestSize();
    if (size == null) {
      return MAX_API_REQUEST_SIZE;
    }
    return size;
  }

  private Method getDispatchMethod(LocalRpcService service, String packageName, String methodName) {
    String dispatchName = Character.toLowerCase(methodName.charAt(0)) + methodName.substring(1);
    String methodId = packageName + "." + dispatchName;
    Method method = methodCache.get(methodId);
    if (method != null) {
      return method;
    }
    for (Method candidate : service.getClass().getMethods()) {
      if (dispatchName.equals(candidate.getName())) {
        methodCache.put(methodId, candidate);
        LatencyPercentiles latencyPercentiles = candidate.getAnnotation(LatencyPercentiles.class);
        if (latencyPercentiles == null) {

          latencyPercentiles = service.getClass().getAnnotation(LatencyPercentiles.class);
        }
        if (latencyPercentiles != null) {
          latencySimulatorCache.put(candidate, new LatencySimulator(latencyPercentiles));
        }
        return candidate;
      }
    }
    throw new CallNotFoundException(packageName, methodName);
  }

  private class AsyncApiCall implements Callable<byte[]> {

    private final Environment environment;
    private final String packageName;
    private final String methodName;
    private final byte[] requestBytes;
    private final Semaphore semaphore;
    private boolean released;

    public AsyncApiCall(Environment environment, String packageName, String methodName,
        byte[] requestBytes, Semaphore semaphore) {
      this.environment = environment;
      this.packageName = packageName;
      this.methodName = methodName;
      this.requestBytes = requestBytes;
      this.semaphore = semaphore;
    }

    @Override
    public byte[] call() {
      try {
        return callInternal();
      } finally {
        tryReleaseSemaphore();
      }
    }

    private byte[] callInternal() {
      ApiProxy.setEnvironmentForCurrentThread(environment);
      try {
        LocalRpcService service = getService(packageName);
        if (service == null) {
          throw new CallNotFoundException(packageName, methodName);
        }
        LocalCapabilitiesEnvironment capEnv = context.getLocalCapabilitiesEnvironment();
        CapabilityStatus capabilityStatus = capEnv
            .getStatusFromMethodName(packageName, methodName);
        if (!CapabilityStatus.ENABLED.equals(capabilityStatus)) {
          throw new ApiProxy.CapabilityDisabledException(
              "Setup in local configuration.", packageName, methodName);
        }

        if (requestBytes.length > getMaxApiRequestSize(service)) {
          throw new RequestTooLargeException(packageName, methodName);
        }

        Method method = getDispatchMethod(service, packageName, methodName);
        Status status = new Status();
        Class<?> requestClass = method.getParameterTypes()[1];
        Object request = convertBytesToPb(requestBytes, requestClass);

        long start = clock.getCurrentTime();
        try {
          return convertPbToBytes(method.invoke(service, status, request));
        } finally {
          LatencySimulator latencySimulator = latencySimulatorCache.get(method);
          if (latencySimulator != null) {
            if (context.getLocalServerEnvironment().simulateProductionLatencies()) {
              latencySimulator.simulateLatency(clock.getCurrentTime() - start, service, request);
            }
          }
        }
      } catch (IllegalAccessException e) {
        throw new UnknownException(packageName, methodName, e);
      } catch (InstantiationException e) {
        throw new UnknownException(packageName, methodName, e);
      } catch (NoSuchMethodException e) {
        throw new UnknownException(packageName, methodName, e);
      } catch (InvocationTargetException e) {
        if (e.getCause() instanceof RuntimeException) {
          throw (RuntimeException) e.getCause();
        }
        throw new UnknownException(packageName, methodName, e.getCause());
      } finally {
        ApiProxy.clearEnvironmentForCurrentThread();
      }
    }

    /**
     * Synchronized method that ensures the semaphore that was claimed for this API call only gets
     * released once.
     */
    synchronized void tryReleaseSemaphore() {
      if (!released && semaphore != null) {
        semaphore.release();
        released = true;
      }
    }
  }

  /**
   * Method needs to be synchronized to ensure that we don't end up starting multiple instances of
   * the same service.  As an example, we've seen a race condition where the local datastore service
   * has not yet been initialized and two datastore requests come in at the exact same time. The
   * first request looks in the service cache, doesn't find it, starts a new local datastore
   * service, registers it in the service cache, and uses that local datastore service to handle the
   * first request.  Meanwhile the second request looks in the service cache, doesn't find it,
   * starts a new local datastore service, registers it in the service cache (stomping on the
   * original one), and uses that local datastore service to handle the second request.  If both of
   * these requests start txns we can end up with 2 requests receiving the same txn id, and that
   * yields all sorts of exciting behavior.  So, we synchronize this method to ensure that we only
   * register a single instance of each service type.
   */
  @Override
  public final synchronized LocalRpcService getService(final String pkg) {
    LocalRpcService cachedService = serviceCache.get(pkg);
    if (cachedService != null) {
      return cachedService;
    }

    return AccessController.doPrivileged(
        new PrivilegedAction<LocalRpcService>() {
          @Override
          public LocalRpcService run() {
            return startServices(pkg);
          }
        });
  }

  @Override
  public DevLogService getLogService() {
    return (DevLogService) getService(DevLogService.PACKAGE);
  }

  private LocalRpcService startServices(String pkg) {
    for (LocalRpcService service : ServiceLoader.load(LocalRpcService.class,
        ApiProxyLocalImpl.class.getClassLoader())) {
      if (service.getPackage().equals(pkg)) {
        service.init(context, properties);
        service.start();
        serviceCache.put(pkg, service);
        return service;
      }
    }
    return null;
  }

  private static Level toJavaLevel(ApiProxy.LogRecord.Level apiProxyLevel) {
    switch (apiProxyLevel) {
      case debug:
        return Level.FINE;
      case info:
        return Level.INFO;
      case warn:
        return Level.WARNING;
      case error:
        return Level.SEVERE;
      case fatal:
        return Level.SEVERE;
      default:
        return Level.WARNING;
    }
  }

  @Override
  public Clock getClock() {
    return clock;
  }

  @Override
  public void setClock(Clock clock) {
    this.clock = clock;
  }

  private static class DaemonThreadFactory implements ThreadFactory {

    private final ThreadFactory parent;

    public DaemonThreadFactory(ThreadFactory parent) {
      this.parent = parent;
    }

    @Override
    public Thread newThread(Runnable r) {
      Thread thread = parent.newThread(r);
      thread.setDaemon(true);
      return thread;
    }
  }
}
TOP

Related Classes of com.google.appengine.tools.development.ApiProxyLocalImpl$PrivilegedApiAction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.