All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.appengine.api.modules.ModulesServiceImpl Maven / Gradle / Ivy

Go to download

API for Google App Engine standard environment with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.appengine.api.modules;

import com.google.appengine.api.modules.ModulesServicePb.GetDefaultVersionRequest;
import com.google.appengine.api.modules.ModulesServicePb.GetDefaultVersionResponse;
import com.google.appengine.api.modules.ModulesServicePb.GetHostnameRequest;
import com.google.appengine.api.modules.ModulesServicePb.GetHostnameResponse;
import com.google.appengine.api.modules.ModulesServicePb.GetModulesRequest;
import com.google.appengine.api.modules.ModulesServicePb.GetModulesResponse;
import com.google.appengine.api.modules.ModulesServicePb.GetNumInstancesRequest;
import com.google.appengine.api.modules.ModulesServicePb.GetNumInstancesResponse;
import com.google.appengine.api.modules.ModulesServicePb.GetVersionsRequest;
import com.google.appengine.api.modules.ModulesServicePb.GetVersionsResponse;
import com.google.appengine.api.modules.ModulesServicePb.ModulesServiceError.ErrorCode;
import com.google.appengine.api.modules.ModulesServicePb.SetNumInstancesRequest;
import com.google.appengine.api.modules.ModulesServicePb.SetNumInstancesResponse;
import com.google.appengine.api.modules.ModulesServicePb.StartModuleRequest;
import com.google.appengine.api.modules.ModulesServicePb.StartModuleResponse;
import com.google.appengine.api.modules.ModulesServicePb.StopModuleRequest;
import com.google.appengine.api.modules.ModulesServicePb.StopModuleResponse;
import com.google.appengine.api.utils.FutureWrapper;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ForwardingFuture;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;

/**
 * Implementation of {@link ModulesService}.
 *
 */
class ModulesServiceImpl implements ModulesService {
  private static final Logger logger = Logger.getLogger(ModulesServiceImpl.class.getName());
  private static final String STARTING_STARTED_MESSAGE =
      "Attempted to start an already started module version, continuing";
  private static final String STOPPING_STOPPED_MESSAGE =
      "Attempted to stop an already stopped module version, continuing";

  // @VisibleForTesting
  static final String PACKAGE = "modules";

  /**
   * Environment attribute key where the instance id is stored.
   *
   * @see ModulesService#getCurrentInstanceId()
   */
  private static final String INSTANCE_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.id";

  @Override
  public String getCurrentModule() {
    return getCurrentEnvironmentOrThrow().getModuleId();
  }

  @Override
  public String getCurrentVersion() {
    Environment env = getCurrentEnvironmentOrThrow();
    return Splitter.on('.').split(env.getVersionId()).iterator().next();
  }

  private static Map getThreadLocalAttributes() {
    return getCurrentEnvironmentOrThrow().getAttributes();
  }

  @Override
  public String getCurrentInstanceId() {
    Map env = getThreadLocalAttributes();
    // We assume that an instance id is not set for automatically-scaled instances.
    if (!env.containsKey(INSTANCE_ID_ENV_ATTRIBUTE)) {
      throw new ModulesException("Instance id unavailable");
    }
    String instanceId = (String) getThreadLocalAttributes().get(INSTANCE_ID_ENV_ATTRIBUTE);
    if (instanceId == null) {
      throw new ModulesException("Instance id unavailable");
    }
    return instanceId;
  }

  /**
   * Returns the result from the provided future in the form suitable for a synchronous call.
   * 

* If {@link Future#get} throws an {@link ExecutionException} *

    *
  1. if {@link ExecutionException#getCause()} is an unchecked exception * including {@link ModulesException} this throws the unchecked exception. *
  2. *
  3. otherwise {@link UndeclaredThrowableException} with the checked * {@link ExecutionException#getCause()} as the cause. *
  4. *
* * If {@link Future#get} throws an {@link InterruptedException} this throws a * {@link ModulesException} with the cause set to the InterruptedException. */ private V getAsyncResult(Future asyncResult) { try { return asyncResult.get(); } catch (InterruptedException ie) { throw new ModulesException("Unexpected failure", ie); } catch (ExecutionException ee) { if (ee.getCause() instanceof RuntimeException) { // Includes ModulesException. throw (RuntimeException) ee.getCause(); } else if (ee.getCause() instanceof Error) { throw (Error) ee.getCause(); } else { throw new UndeclaredThrowableException(ee.getCause()); } } } @Override public Set getModules() { return getAsyncResult(getModulesAsync()); } private Future> getModulesAsync() { GetModulesRequest.Builder requestBuilder = GetModulesRequest.newBuilder(); Future> result = new ModulesServiceFutureWrapper>("GetModules", requestBuilder) { @Override protected Set wrap(byte[] key) throws InvalidProtocolBufferException { GetModulesResponse.Builder responseBuilder = GetModulesResponse.newBuilder(); responseBuilder.mergeFrom(key); return Sets.newHashSet(responseBuilder.getModuleList()); } }; return result; } @Override public Set getVersions(String module) { return getAsyncResult(getVersionsAsync(module)); } private Future> getVersionsAsync(String module) { GetVersionsRequest.Builder requestBuilder = GetVersionsRequest.newBuilder(); if (module != null) { requestBuilder.setModule(module); } Future> result = new ModulesServiceFutureWrapper>("GetVersions", requestBuilder) { @Override protected Set wrap(byte[] key) throws InvalidProtocolBufferException { GetVersionsResponse.Builder responseBuilder = GetVersionsResponse.newBuilder(); responseBuilder.mergeFrom(key); return Sets.newHashSet(responseBuilder.getVersionList()); } }; return result; } @Override public String getDefaultVersion(String module) { return getAsyncResult(getDefaultVersionAsync(module)); } private Future getDefaultVersionAsync(String module) { GetDefaultVersionRequest.Builder requestBuilder = GetDefaultVersionRequest.newBuilder(); if (module != null) { requestBuilder.setModule(module); } Future result = new ModulesServiceFutureWrapper("GetDefaultVersion", requestBuilder) { @Override protected String wrap(byte[] key) throws InvalidProtocolBufferException { GetDefaultVersionResponse.Builder responseBuilder = GetDefaultVersionResponse.newBuilder(); responseBuilder.mergeFrom(key); return responseBuilder.getVersion(); } }; return result; } @Override public int getNumInstances(String module, String version) { return getAsyncResult(getNumInstancesAsync(module, version)); } private Future getNumInstancesAsync(String module, String version) { GetNumInstancesRequest.Builder requestBuilder = GetNumInstancesRequest.newBuilder(); if (module != null) { requestBuilder.setModule(module); } if (version != null) { requestBuilder.setVersion(version); } Future result = new ModulesServiceFutureWrapper("GetNumInstances", requestBuilder) { @Override protected Integer wrap(byte[] key) throws InvalidProtocolBufferException { GetNumInstancesResponse.Builder responseBuilder = GetNumInstancesResponse.newBuilder(); responseBuilder.mergeFrom(key); if (responseBuilder.getInstances() < 0 || responseBuilder.getInstances() > Integer.MAX_VALUE) { throw new IllegalStateException("Invalid instances value: " + responseBuilder.getInstances()); } return (int) responseBuilder.getInstances(); } }; return result; } @Override public void setNumInstances(String module, String version, long instances) { getAsyncResult(setNumInstancesAsync(module, version, instances)); } @Override public Future setNumInstancesAsync(String module, String version, long instances) { SetNumInstancesRequest.Builder requestBuilder = SetNumInstancesRequest.newBuilder(); if (module != null) { requestBuilder.setModule(module); } if (version != null) { requestBuilder.setVersion(version); } requestBuilder.setInstances(instances); Future result = new ModulesServiceFutureWrapper("SetNumInstances", requestBuilder) { @Override protected Void wrap(byte[] key) throws InvalidProtocolBufferException { SetNumInstancesResponse.Builder responseBuilder = SetNumInstancesResponse.newBuilder(); responseBuilder.mergeFrom(key); return null; } }; return result; } @Override public void startVersion(String module, String version) { getAsyncResult(startVersionAsync(module, version)); } @Override public Future startVersionAsync(String module, String version) { StartModuleRequest.Builder requestBuilder = StartModuleRequest.newBuilder(); requestBuilder.setModule(module); requestBuilder.setVersion(version); Future modulesServiceFuture = new ModulesServiceFutureWrapper("StartModule", requestBuilder) { @Override protected Void wrap(byte[] key) throws InvalidProtocolBufferException { StartModuleResponse.Builder responseBuilder = StartModuleResponse.newBuilder(); responseBuilder.mergeFrom(key); return null; } }; return new IgnoreUnexpectedStateExceptionFuture(modulesServiceFuture, STARTING_STARTED_MESSAGE); } @Override public void stopVersion(String module, String version) { getAsyncResult(stopVersionAsync(module, version)); } @Override public Future stopVersionAsync(String module, String version) { StopModuleRequest.Builder requestBuilder = StopModuleRequest.newBuilder(); if (module != null) { requestBuilder.setModule(module); } if (version != null) { requestBuilder.setVersion(version); } Future modulesServiceFuture = new ModulesServiceFutureWrapper("StopModule", requestBuilder) { @Override protected Void wrap(byte[] key) throws InvalidProtocolBufferException { StopModuleResponse.Builder responseBuilder = StopModuleResponse.newBuilder(); responseBuilder.mergeFrom(key); return null; } }; return new IgnoreUnexpectedStateExceptionFuture(modulesServiceFuture, STOPPING_STOPPED_MESSAGE); } private Future getHostnameAsync(GetHostnameRequest.Builder requestBuilder) { Future result = new ModulesServiceFutureWrapper("GetHostname", requestBuilder) { @Override protected String wrap(byte[] key) throws InvalidProtocolBufferException { GetHostnameResponse.Builder responseBuilder = GetHostnameResponse.newBuilder(); responseBuilder.mergeFrom(key); return responseBuilder.getHostname(); } }; return result; } private GetHostnameRequest.Builder newGetHostnameRequestBuilder(String module, String version) { GetHostnameRequest.Builder builder = GetHostnameRequest.newBuilder(); if (module != null) { builder.setModule(module); } if (version != null) { builder.setVersion(version); } return builder; } @Override public String getVersionHostname(String module, String version) { return getAsyncResult(getVersionHostnameAsync(module, version)); } private Future getVersionHostnameAsync(String module, String version) { GetHostnameRequest.Builder requestBuilder = newGetHostnameRequestBuilder(module, version); return getHostnameAsync(requestBuilder); } @Override public String getInstanceHostname(String module, String version, String instance) { return getAsyncResult(getInstanceHostnameAsync(module, version, instance)); } private Future getInstanceHostnameAsync(String module, String version, String instance) { GetHostnameRequest.Builder requestBuilder = newGetHostnameRequestBuilder(module, version); requestBuilder.setInstance(instance); return getHostnameAsync(requestBuilder); } // Only allow instantiation by the ModulesServiceFactoryImpl. ModulesServiceImpl() { } private abstract static class ModulesServiceFutureWrapper extends FutureWrapper { private final String method; public ModulesServiceFutureWrapper(String method, Message.Builder request) { super(ApiProxy.makeAsyncCall(PACKAGE, method, request.build().toByteArray())); this.method = method; } @Override protected Throwable convertException(Throwable cause) { if (cause instanceof ApiProxy.ApplicationException) { return convertApplicationException(method, (ApiProxy.ApplicationException) cause); } else if (cause instanceof InvalidProtocolBufferException){ return new ModulesException("Unexpected failure", cause); } else { return cause; } } private RuntimeException convertApplicationException(String method, ApiProxy.ApplicationException e) { switch (ErrorCode.forNumber(e.getApplicationError())) { case INVALID_MODULE: @SuppressWarnings("deprecation") RuntimeException result1 = new ModulesException("Unknown module"); return result1; case INVALID_VERSION: @SuppressWarnings("deprecation") RuntimeException result2 = new ModulesException("Unknown module version"); return result2; case INVALID_INSTANCES: @SuppressWarnings("deprecation") RuntimeException result3 = new ModulesException("Invalid instance"); return result3; case UNEXPECTED_STATE: if (method.equals("StartModule") || method.equals("StopModule")) { return new UnexpectedStateException("Unexpected state for method " + method); } else { return new ModulesException("Unexpected state with method '" + method + "'"); } default: return new ModulesException("Unknown error: '" + e.getApplicationError() + "'"); } } } // TODO: When b/12034257 is fixed remove this wrapper and use the new // com.google.appengine.api.utils.FutureWrapper function. /** * Delegating {@link Future} to ignore {@link UnexpectedStateException} in calls to {@link * #get} and {@link #get(long, TimeUnit)}. */ @SuppressWarnings("javadoc") private static class IgnoreUnexpectedStateExceptionFuture extends ForwardingFuture { private final Future delegate; private final String logMessage; IgnoreUnexpectedStateExceptionFuture(Future delegate, String logMessage) { this.delegate = delegate; this.logMessage = logMessage; } @Override protected Future delegate() { return delegate; } @Override public Void get() throws InterruptedException, ExecutionException { try { return delegate.get(); } catch (ExecutionException ee) { return throwOriginalUnlessUnexpectedState(ee); } } @Override public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return delegate.get(timeout, unit); } catch (ExecutionException ee) { return throwOriginalUnlessUnexpectedState(ee); } } private Void throwOriginalUnlessUnexpectedState(ExecutionException original) throws ExecutionException { Throwable cause = original.getCause(); if (cause instanceof UnexpectedStateException) { logger.info(logMessage); return null; } else { throw original; } } } private static ApiProxy.Environment getCurrentEnvironmentOrThrow() { ApiProxy.Environment environment = ApiProxy.getCurrentEnvironment(); if (environment == null) { throw new IllegalStateException( "Operation not allowed in a thread that is neither the original request thread " + "nor a thread created by ThreadManager"); } return environment; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy