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

org.glowroot.agent.shaded.grpc.internal.ServerCallImpl Maven / Gradle / Ivy

There is a newer version: 0.9.24
Show newest version
/*
 * Copyright 2015, Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *
 *    * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.glowroot.agent.shaded.grpc.internal;

import static org.glowroot.agent.shaded.google.common.base.Preconditions.checkArgument;
import static org.glowroot.agent.shaded.google.common.base.Preconditions.checkNotNull;
import static org.glowroot.agent.shaded.google.common.base.Preconditions.checkState;
import static org.glowroot.agent.shaded.grpc.internal.GrpcUtil.ACCEPT_ENCODING_JOINER;
import static org.glowroot.agent.shaded.grpc.internal.GrpcUtil.ACCEPT_ENCODING_SPLITER;
import static org.glowroot.agent.shaded.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
import static org.glowroot.agent.shaded.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;

import org.glowroot.agent.shaded.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.google.common.base.Throwables;

import org.glowroot.agent.shaded.grpc.Codec;
import org.glowroot.agent.shaded.grpc.Compressor;
import org.glowroot.agent.shaded.grpc.CompressorRegistry;
import org.glowroot.agent.shaded.grpc.Context;
import org.glowroot.agent.shaded.grpc.Decompressor;
import org.glowroot.agent.shaded.grpc.DecompressorRegistry;
import org.glowroot.agent.shaded.grpc.Metadata;
import org.glowroot.agent.shaded.grpc.MethodDescriptor;
import org.glowroot.agent.shaded.grpc.MethodDescriptor.MethodType;
import org.glowroot.agent.shaded.grpc.ServerCall;
import org.glowroot.agent.shaded.grpc.Status;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;

final class ServerCallImpl extends ServerCall {
  private final ServerStream stream;
  private final MethodDescriptor method;
  private final Context.CancellableContext context;
  private Metadata inboundHeaders;
  private final DecompressorRegistry decompressorRegistry;
  private final CompressorRegistry compressorRegistry;

  // state
  private volatile boolean cancelled;
  private boolean sendHeadersCalled;
  private boolean closeCalled;
  private Compressor compressor;

  ServerCallImpl(ServerStream stream, MethodDescriptor method,
      Metadata inboundHeaders, Context.CancellableContext context,
      DecompressorRegistry decompressorRegistry, CompressorRegistry compressorRegistry) {
    this.stream = stream;
    this.method = method;
    this.context = context;
    this.inboundHeaders = inboundHeaders;
    this.decompressorRegistry = decompressorRegistry;
    this.compressorRegistry = compressorRegistry;

    if (inboundHeaders.containsKey(MESSAGE_ENCODING_KEY)) {
      String encoding = inboundHeaders.get(MESSAGE_ENCODING_KEY);
      Decompressor decompressor = decompressorRegistry.lookupDecompressor(encoding);
      if (decompressor == null) {
        throw Status.INTERNAL
            .withDescription(String.format("Can't find decompressor for %s", encoding))
            .asRuntimeException();
      }
      stream.setDecompressor(decompressor);
    }
  }

  @Override
  public void request(int numMessages) {
    stream.request(numMessages);
  }

  @Override
  public void sendHeaders(Metadata headers) {
    checkState(!sendHeadersCalled, "sendHeaders has already been called");
    checkState(!closeCalled, "call is closed");

    headers.removeAll(MESSAGE_ENCODING_KEY);
    if (compressor == null) {
      compressor = Codec.Identity.NONE;
      if (inboundHeaders.containsKey(MESSAGE_ACCEPT_ENCODING_KEY)) {
        String acceptEncodings = inboundHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY);
        for (String acceptEncoding : ACCEPT_ENCODING_SPLITER.split(acceptEncodings)) {
          Compressor c = compressorRegistry.lookupCompressor(acceptEncoding);
          if (c != null) {
            compressor = c;
            break;
          }
        }
      }
    } else {
      if (inboundHeaders.containsKey(MESSAGE_ACCEPT_ENCODING_KEY)) {
        String acceptEncodings = inboundHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY);
        List acceptedEncodingsList = ACCEPT_ENCODING_SPLITER.splitToList(acceptEncodings);
        if (!acceptedEncodingsList.contains(compressor.getMessageEncoding())) {
          // resort to using no compression.
          compressor = Codec.Identity.NONE;
        }
      } else {
        compressor = Codec.Identity.NONE;
      }
    }
    inboundHeaders = null;
    if (compressor != Codec.Identity.NONE) {
      headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding());
    }
    stream.setCompressor(compressor);

    headers.removeAll(MESSAGE_ACCEPT_ENCODING_KEY);
    Set acceptEncodings = decompressorRegistry.getAdvertisedMessageEncodings();
    if (!acceptEncodings.isEmpty()) {
      headers.put(MESSAGE_ACCEPT_ENCODING_KEY, ACCEPT_ENCODING_JOINER.join(acceptEncodings));
    }

    // Don't check if sendMessage has been called, since it requires that sendHeaders was already
    // called.
    sendHeadersCalled = true;
    stream.writeHeaders(headers);
  }

  @Override
  public void sendMessage(RespT message) {
    checkState(sendHeadersCalled, "sendHeaders has not been called");
    checkState(!closeCalled, "call is closed");
    try {
      InputStream resp = method.streamResponse(message);
      stream.writeMessage(resp);
      stream.flush();
    } catch (Throwable t) {
      close(Status.fromThrowable(t), new Metadata());
      throw Throwables.propagate(t);
    }
  }

  @Override
  public void setMessageCompression(boolean enable) {
    stream.setMessageCompression(enable);
  }

  @Override
  public void setCompression(String compressorName) {
    // Added here to give a better error message.
    checkState(!sendHeadersCalled, "sendHeaders has been called");

    compressor = compressorRegistry.lookupCompressor(compressorName);
    checkArgument(compressor != null, "Unable to find compressor by name %s", compressorName);
  }

  @Override
  public boolean isReady() {
    return stream.isReady();
  }

  @Override
  public void close(Status status, Metadata trailers) {
    checkState(!closeCalled, "call already closed");
    closeCalled = true;
    inboundHeaders = null;
    stream.close(status, trailers);
  }

  @Override
  public boolean isCancelled() {
    return cancelled;
  }

  ServerStreamListener newServerStreamListener(ServerCall.Listener listener,
      Future timeout) {
    return new ServerStreamListenerImpl(this, listener, timeout, context);
  }

  /**
   * All of these callbacks are assumed to called on an application thread, and the caller is
   * responsible for handling thrown exceptions.
   */
  @VisibleForTesting
  static final class ServerStreamListenerImpl implements ServerStreamListener {
    private final ServerCallImpl call;
    private final ServerCall.Listener listener;
    private final Future timeout;
    private final Context.CancellableContext context;
    private boolean messageReceived;

    public ServerStreamListenerImpl(
        ServerCallImpl call, ServerCall.Listener listener, Future timeout,
        Context.CancellableContext context) {
      this.call = checkNotNull(call, "call");
      this.listener = checkNotNull(listener, "listener must not be null");
      this.timeout = checkNotNull(timeout, "timeout");
      this.context = checkNotNull(context, "context");
    }

    @Override
    public void messageRead(final InputStream message) {
      try {
        if (call.cancelled) {
          return;
        }
        // Special case for unary calls.
        if (messageReceived && call.method.getType() == MethodType.UNARY) {
          call.stream.close(Status.INVALID_ARGUMENT.withDescription(
                  "More than one request messages for unary call or server streaming call"),
              new Metadata());
          return;
        }
        messageReceived = true;

        listener.onMessage(call.method.parseRequest(message));
      } finally {
        try {
          message.close();
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    }

    @Override
    public void halfClosed() {
      if (call.cancelled) {
        return;
      }

      listener.onHalfClose();
    }

    @Override
    public void closed(Status status) {
      timeout.cancel(true);
      try {
        if (status.isOk()) {
          listener.onComplete();
        } else {
          call.cancelled = true;
          listener.onCancel();
        }
      } finally {
        // Cancel context after delivering RPC closure notification to allow the application to
        // clean up and update any state based on whether onComplete or onCancel was called.
        context.cancel(null);
      }
    }

    @Override
    public void onReady() {
      if (call.cancelled) {
        return;
      }
      listener.onReady();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy