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

org.apache.pekko.grpc.internal.MetadataImpl.scala Maven / Gradle / Ivy

Go to download

Apache Pekko gRPC - Support for building streaming gRPC servers and clients on top of Pekko Streams.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) 2009-2021 Lightbend Inc. 
 */

package org.apache.pekko.grpc.internal

import java.util.{ List => jList, Locale, Map => jMap, Optional }

import scala.collection.JavaConverters._
import scala.collection.immutable
import org.apache.pekko
import pekko.annotation.InternalApi
import pekko.http.scaladsl.model.HttpHeader
import pekko.japi.Pair
import pekko.util.ByteString
import pekko.grpc.scaladsl.{ BytesEntry, Metadata, MetadataEntry, StringEntry }
import pekko.grpc.javadsl
import pekko.util.OptionConverters._

@InternalApi private[pekko] object MetadataImpl {
  val BINARY_SUFFIX: String = io.grpc.Metadata.BINARY_HEADER_SUFFIX

  val empty = new MetadataImpl(List.empty)

  def scalaMetadataFromGoogleGrpcMetadata(mutableMetadata: io.grpc.Metadata): Metadata =
    new GrpcMetadataImpl(mutableMetadata)

  def javaMetadataFromGoogleGrpcMetadata(mutableMetadata: io.grpc.Metadata): javadsl.Metadata =
    new JavaMetadataImpl(scalaMetadataFromGoogleGrpcMetadata(mutableMetadata))

  def encodeBinaryHeader(bytes: ByteString): String = bytes.encodeBase64.utf8String

  def decodeBinaryHeader(value: String): ByteString = ByteString(value).decodeBase64

  def toMap(list: List[(String, MetadataEntry)]): Map[String, List[MetadataEntry]] = {
    // This method is complicated by the changes to mapValues in scala 2.13.

    // For Scala 2.12, this should be:
    // list.groupBy(_._1).mapValues(_.map(_._2)).toMap

    // For scala 2.13, Map.mapValues is deprecated. The suggested migration is:
    // list.groupBy(_._1).view.mapValues(_.map(_._2)).toMap

    // Even better would be:
    // list.groupMap(_._1)(_._2)

    // For now, drop back to map() to deal with the incompatibility between versions.
    list.groupBy(_._1).map(t => (t._1, t._2.map(_._2))).toMap
  }

  def niceStringRep(metadata: Map[String, List[MetadataEntry]]) = {
    val data = metadata.map { case (key, values) => key + " -> " + values.mkString("[", ", ", "]") }.mkString(", ")
    s"Metadata($data)"
  }
}

@InternalApi private[pekko] final class MetadataImpl(val entries: List[(String, MetadataEntry)]) {
  def addEntry(key: String, value: String): MetadataImpl = {
    if (key.endsWith(MetadataImpl.BINARY_SUFFIX))
      throw new IllegalArgumentException(s"String header names must not end with '${MetadataImpl.BINARY_SUFFIX}'")
    new MetadataImpl((key -> StringEntry(value)) :: entries)
  }

  def addEntry(key: String, value: ByteString): MetadataImpl = {
    if (!key.endsWith(MetadataImpl.BINARY_SUFFIX))
      throw new IllegalArgumentException(s"Binary headers names must end with '${MetadataImpl.BINARY_SUFFIX}'")
    new MetadataImpl((key -> BytesEntry(value)) :: entries)
  }

  def toGoogleGrpcMetadata(): io.grpc.Metadata = {
    val mutableMetadata = new io.grpc.Metadata()
    entries.reverseIterator.foreach {
      case (key, entry) =>
        entry match {
          case StringEntry(value) =>
            mutableMetadata.put(io.grpc.Metadata.Key.of(key, io.grpc.Metadata.ASCII_STRING_MARSHALLER), value)

          case BytesEntry(value) =>
            mutableMetadata.put(io.grpc.Metadata.Key.of(key, io.grpc.Metadata.BINARY_BYTE_MARSHALLER), value.toArray)
        }
    }
    mutableMetadata
  }
}

/**
 * This class wraps a mutable Metadata from io.grpc with the Scala Metadata interface.
 * @param delegate The underlying mutable metadata.
 */
@InternalApi
class GrpcMetadataImpl(delegate: io.grpc.Metadata) extends Metadata {
  private lazy val map = delegate.keys.iterator.asScala.map(key => key -> getEntries(key)).toMap

  override def getText(key: String): Option[String] =
    Option(delegate.get(textKey(key)))

  override def getBinary(key: String): Option[ByteString] =
    Option(delegate.get(binaryKey(key))).map(ByteString.fromArray)

  override def asMap: Map[String, List[MetadataEntry]] =
    map

  override def asList: List[(String, MetadataEntry)] = {
    delegate.keys.iterator.asScala.flatMap(key => getEntries(key).map(entry => (key, entry))).toList
  }

  override def toString: String =
    MetadataImpl.niceStringRep(map)

  private def binaryKey(key: String): io.grpc.Metadata.Key[Array[Byte]] =
    io.grpc.Metadata.Key.of(key, io.grpc.Metadata.BINARY_BYTE_MARSHALLER)

  private def textKey(key: String): io.grpc.Metadata.Key[String] =
    io.grpc.Metadata.Key.of(key, io.grpc.Metadata.ASCII_STRING_MARSHALLER)

  private def getEntries(key: String): List[MetadataEntry] =
    if (key.endsWith(io.grpc.Metadata.BINARY_HEADER_SUFFIX)) {
      delegate.getAll(binaryKey(key)).asScala.map(b => BytesEntry(ByteString.fromArray(b))).toList
    } else {
      delegate.getAll(textKey(key)).asScala.map(StringEntry.apply).toList
    }
}

/**
 * This class represents metadata as a list of (key, entry) tuples.
 * @param entries The list of (key, entry) tuples.
 */
@InternalApi
class EntryMetadataImpl(entries: List[(String, MetadataEntry)] = Nil) extends Metadata {
  override def getText(key: String): Option[String] =
    entries.reverseIterator.collectFirst {
      case (name, StringEntry(value)) if name == key => value
    }

  override def getBinary(key: String): Option[ByteString] =
    entries.reverseIterator.collectFirst {
      case (name, BytesEntry(value)) if name == key => value
    }

  override def asMap: Map[String, List[MetadataEntry]] =
    MetadataImpl.toMap(entries)

  override def asList: List[(String, MetadataEntry)] =
    entries

  override def toString: String =
    MetadataImpl.niceStringRep(asMap)
}

/**
 * This class wraps a list of headers from an HttpResponse with the Metadata interface.
 * @param headers The list of HTTP response headers.
 */
@InternalApi
class HeaderMetadataImpl(headers: immutable.Seq[HttpHeader] = immutable.Seq.empty) extends Metadata {
  private lazy val map: Map[String, List[MetadataEntry]] =
    MetadataImpl.toMap(asList)

  override def getText(key: String): Option[String] = {
    val lcKey = key.toLowerCase(Locale.ROOT)
    headers.reverseIterator.collectFirst {
      case header if header.is(lcKey) => header.value
    }
  }

  override def getBinary(key: String): Option[ByteString] = {
    val lcKey = key.toLowerCase(Locale.ROOT)
    headers.reverseIterator.collectFirst {
      case header if header.is(lcKey) =>
        MetadataImpl.decodeBinaryHeader(header.value)
    }
  }

  override def asMap: Map[String, List[MetadataEntry]] =
    map

  override def asList: List[(String, MetadataEntry)] =
    headers.map(toKeyEntry).toList

  override def toString: String =
    MetadataImpl.niceStringRep(asMap)

  private def toKeyEntry(header: HttpHeader): (String, MetadataEntry) = {
    val key = header.lowercaseName()
    val entry =
      if (key.endsWith(MetadataImpl.BINARY_SUFFIX)) {
        val bytes = MetadataImpl.decodeBinaryHeader(header.value)
        BytesEntry(bytes)
      } else {
        val text = header.value
        StringEntry(text)
      }
    (key, entry)
  }
}

/**
 * This class wraps a scaladsl.Metadata instance with the javadsl.Metadata interface.
 * @param delegate The underlying Scala metadata instance.
 */
@InternalApi
class JavaMetadataImpl(delegate: Metadata) extends javadsl.Metadata {
  override def getText(key: String): Optional[String] =
    delegate.getText(key).toJava

  override def getBinary(key: String): Optional[ByteString] =
    delegate.getBinary(key).toJava

  override def asMap(): jMap[String, jList[javadsl.MetadataEntry]] = {
    // This method is also affected by incompatible changes between scala 2.12 and 2.13. (See comment in
    // MetadataImp.toMap for more details.)

    // For now, as a workaround, implement the conversion in terms of map instead of mapValues.
    delegate.asMap.map(t => t._1 -> t._2.map(_.asInstanceOf[javadsl.MetadataEntry]).asJava).toMap.asJava
  }

  override def asList(): jList[Pair[String, javadsl.MetadataEntry]] =
    delegate.asList.map(t => Pair[String, javadsl.MetadataEntry](t._1, t._2)).asJava

  override def asScala: Metadata =
    delegate

  override def toString: String =
    delegate.toString
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy