【Scala】Scala ~ AWS SDK / サンプル ~

■ はじめに

https://dk521123.hatenablog.com/entry/2023/03/24/211033

の続き。

今回は、Scalaで、AWS SDK for Java を使ったサンプル集

目次

【1】S3
 1)S3をハンドリングする共通処理
【2】Secrets Manager
 1)Secrets Managerをハンドリングする共通処理

【1】S3

1)S3をハンドリングする共通処理

import scala.util.control.Breaks._
import scala.collection.JavaConversions._
import scala.reflect._

import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response
import software.amazon.awssdk.services.s3.model.S3Object
import software.amazon.awssdk.services.s3.model.GetObjectRequest

import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.constructor.Constructor

import java.io.File
import java.io.ByteArrayInputStream


class S3Utils(val region: Region = Region.US_WEST_2) {
  val s3Client = S3Client.builder().region(region).build()

  def getFileList(bucketName: String, s3Key: String): Seq[S3FileObject] = {
    // See https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/model/ListObjectsV2Request.html
    var listObjectsReqManual = ListObjectsV2Request.builder()
      .bucket(bucketName)
      .prefix(s3Key)
      .build()

    var fileList = Seq[S3FileObject]()
    breakable {
      while (true) {
        // See https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
        val listObjResponse = s3Client.listObjectsV2(listObjectsReqManual)

        // See https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/model/S3Object.html
        for (content <- listObjResponse.contents()) {
          fileList = fileList :+ new S3FileObject(
            bucketName,
            content.key,
            content.size
          )
        }
        val nextToken = listObjResponse.nextContinuationToken()
        if (nextToken == null) {
          // Done
          break
        }
        // Set next token and get next list
        listObjectsReqManual = listObjectsReqManual.toBuilder()
          .continuationToken(nextToken)
          .build()
      }
    }
    fileList
  }

  def readYaml[T: ClassTag](bucketName: String, s3Key: String): T = {
    val fileContentAsBytes = getS3FileBytes(bucketName, s3Key)
    readYaml[T](fileContentAsBytes)
  }

  def readYaml[T: ClassTag](inputPath: Array[Byte]): T = {
    val reader = new ByteArrayInputStream(inputPath)
    try {
      // e.g. "class ProcessInfo"
      val targetClass = classTag[T].runtimeClass.toString()
      // e.g. "class ProcessInfo" -> "ProcessInfo"
      val className = targetClass.replaceFirst("class ", "")

      val inputYaml = new Yaml(new Constructor(className))
      return inputYaml.load(reader).asInstanceOf[T]
    } finally {
      reader.close()
    }
  }

  // https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/example_s3_GetObject_section.html  
  def getS3FileBytes(bucketName: String, s3Key: String): Array[Byte] = {
    val objectRequest = GetObjectRequest
      .builder()
      .key(s3Key)
      .bucket(bucketName)
      .build()
    val objectBytes = s3Client.getObjectAsBytes(objectRequest)
     objectBytes.asByteArray()
  }
}

class S3FileObject(
  val bucketName: String, val s3Key: String, val fileSize: Long
  ) {
    def getFullPath : String = s"s3://${bucketName}/${s3Key}"
}

【2】Secrets Manager

1)Secrets Managerをハンドリングする共通処理

import java.nio.ByteBuffer
import com.amazonaws.AmazonClientException
import com.amazonaws.AmazonServiceException
import com.amazonaws.client.builder.AwsClientBuilder
import com.amazonaws.auth.profile.ProfileCredentialsProvider
import com.amazonaws.AmazonWebServiceRequest
import com.amazonaws.services.secretsmanager._
import com.amazonaws.services.secretsmanager.model._

class SecretsManagerUtils(
  endpoint: String = "secretsmanager.us-west-2.amazonaws.com",
  region: String = "us-west-2") {
  val config = new AwsClientBuilder.EndpointConfiguration(endpoint, region)
  val clientBuilder = AWSSecretsManagerClientBuilder.standard()
  clientBuilder.setEndpointConfiguration(config)
  // AWSSecretsManager
  val secretsClient = clientBuilder.build()

  def getSecretValue(secretName: String): String = {
    val secretValueResponse = getSecretValueResponse(secretName)
    if(secretValueResponse == null) {
      return ""
    }

    val secretValue = secretValueResponse.getSecretString()
    if (secretValue == null) "" else secretValue
  }

  def getSecretValueAsBytes(secretName: String): Array[Byte] = {
    val secretValueResponse = getSecretValueResponse(secretName)
    if(secretValueResponse == null) {
      return new Array[Byte](0)
    }
    val secretValue = secretValueResponse.getSecretBinary()
    val secretValueAsBytes = new Array[Byte](secretValue.remaining)
    secretValue.get(secretValueAsBytes)

    secretValueAsBytes
  }

  def getSecretValueResponse(secretName: String): GetSecretValueResult = {
    val secretValueRequest = new GetSecretValueRequest()
              .withSecretId(secretName)
              .withVersionStage("AWSCURRENT")
    secretsClient.getSecretValue(secretValueRequest)
  }
}

関連記事

ScalaAWS SDK
https://dk521123.hatenablog.com/entry/2023/03/24/211033
Scala ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/03/10/193805
Scala ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/12/184331
Scala ~ 基本編 / 繰り返し ~
https://dk521123.hatenablog.com/entry/2023/01/24/000000
Scala ~ 基本編 / Option型 ~
https://dk521123.hatenablog.com/entry/2023/03/09/000000
Scala ~ 基本編 / メソッド ~
https://dk521123.hatenablog.com/entry/2023/03/03/000000
Scala ~ 基本編 / クラス ~
https://dk521123.hatenablog.com/entry/2023/03/14/000857
Scala ~ 基本編 / コレクション ~
https://dk521123.hatenablog.com/entry/2023/03/13/000345
Scala ~ 基本編 / 日付・日時 ~
https://dk521123.hatenablog.com/entry/2023/03/08/000000
Scala ~ 基本編 / 正規表現
https://dk521123.hatenablog.com/entry/2023/03/18/034704
Scala ~ 基本編 / ジェネリック
https://dk521123.hatenablog.com/entry/2023/03/21/003817
Scala ~ ファイル名・パスの扱い ~
https://dk521123.hatenablog.com/entry/2023/03/11/000000
Scala ~ ファイルハンドリング ~
https://dk521123.hatenablog.com/entry/2023/01/03/000000
ScalaYAML
https://dk521123.hatenablog.com/entry/2023/03/16/012034
JavaでEmail ~ JavaMail / Text ~
https://dk521123.hatenablog.com/entry/2016/07/16/222422
JavaでEmail ~ JavaMail / 添付ファイル ~
https://dk521123.hatenablog.com/entry/2016/07/17/023459
JavaでEmail ~ SMTP認証 ~
https://dk521123.hatenablog.com/entry/2016/11/07/215251
JavaでEmail ~ SMTP認証 / DIGEST-MD5
https://dk521123.hatenablog.com/entry/2016/12/07/222229
JavaでEmail ~ JavaMail / TLS
https://dk521123.hatenablog.com/entry/2017/05/03/163219
JavaでEmail ~ JavaMail / Return-Path・Errors-To ~
https://dk521123.hatenablog.com/entry/2017/05/07/000344
Amazon SES ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2017/04/28/234103
Amazon S3 ~ Boto3編 ~
https://dk521123.hatenablog.com/entry/2019/10/21/230004
Amazon S3 ~ Boto3でファイル存在チェック ~
https://dk521123.hatenablog.com/entry/2022/02/26/182526
AWS Glue ~ Scalaでの実装 ~
https://dk521123.hatenablog.com/entry/2023/03/17/000000
AWS Glue ~ ローカル環境を作成する / Glue v3.0版 ~
https://dk521123.hatenablog.com/entry/2022/01/31/165650
LocalStack ~ ローカルで疑似AWSを作成する ~
https://dk521123.hatenablog.com/entry/2019/12/14/010524
LocalStack ~ ローカルで疑似Lambda/S3/DynamoDBを作成する ~
https://dk521123.hatenablog.com/entry/2019/12/16/231149

【Scala】ScalaTest ~ with Mockito ~

■ はじめに

https://dk521123.hatenablog.com/entry/2023/03/27/001306
https://dk521123.hatenablog.com/entry/2023/03/28/003906
https://dk521123.hatenablog.com/entry/2023/03/29/000014

の続き。

Scala の単体試験で、ScalaTest をベースにやってきたが、
今回は、ScalaTest + Mockito  (org.scalatestplus) で
モックを使ったテストしてみる。

目次

【0】ScalaTest の Mock
【1】Mockito
【2】初期設定
 補足:mockito-scala
【3】サンプル
 例1:mock() / when().thenReturn()
 例2:verify()
 補足:公式ドキュメントのサンプル

【0】ScalaTest の Mock

はじめ、ScalaTestでのMockは、Mockito の1択だろって
思っていたのだが、デフォルトで「import org.scalatest.mock.MockitoSugar」
がなくなっていたしで、改めてちゃんと調べてみたら、
公式ドキュメントに以下の記述あり。

https://www.scalatest.org/user_guide/testing_with_mock_objects

より抜粋
~~~~~~~~~~~~
ScalaMock
EasyMock
JMock
Mockito
~~~~~~~~~~~~

どれを選ぶかは、自分で使ってみて使い心地がいい方でいいと思うが
とりあえず、今回は、情報量が多い「Mockito」を使ってみる
(「EasyMock」と「Mockito」は、過去の自分が使っていたし)

ScalaMock

* PureのScala製のMock

http://scalamock.org/

EasyMock

* Java製のMock
 => 以下の関連記事を参照のこと

https://dk521123.hatenablog.com/entry/2010/01/28/234207

* 導入したい場合は、以下の公式ドキュメント参照

https://www.scalatest.org/plus/easymock

JMock

* こちらもJava製のMock

http://jmock.org/

* 導入したい場合は、以下の公式ドキュメント参照

https://www.scalatest.org/plus/jmock

【1】Mockito

* Java の モックツール
 => 詳細は、以下の関連記事を参照のこと

Mockito ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2014/07/18/233904
Mockito ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2014/07/19/121409

【2】初期設定

https://www.scalatest.org/plus/mockito

libraryDependencies ++= Seq(
  "org.scalatestplus" %% "mockito-4-6" % "3.2.15.0" % "test",
  "org.scalatest" %% "scalatest" % "3.2.15" % "test"
)

https://mvnrepository.com/artifact/org.scalatestplus
https://mvnrepository.com/artifact/org.scalatestplus/mockito-4-6

補足:mockito-scala

* 上記とは、別に「mockito-scala」ってのもある。
 => こっちの方がよさげ??

https://github.com/mockito/mockito-scala

libraryDependencies ++= Seq(
  "org.mockito" %% "mockito-scala" % "1.17.14" % "test"
)

https://takezoe.hatenablog.com/entry/2020/11/03/004143

【3】サンプル

例1:mock() / when().thenReturn()

import org.scalatest.funspec.AnyFunSpec
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito.when

class Hello {
  def sayHello(name: String): String = {
    s"Hello, ${name}"
  }
}

// MockitoSugar trait provides some basic syntax sugar for Mockito.
class HelloTest extends AnyFunSpec with MockitoSugar {
  describe("For Hello World Test") {
    it ("Hello World For Mockito") {
      // First, create the mock object (mock オブジェクト作成用メソッド)
      val mockedHello = mock[Hello]

      // when().thenReturn(): 戻り値ありのメソッドに振る舞いを設定
      //  => 戻り値を「Fixed value」に変更
      when(mockedHello.sayHello("Mike")).thenReturn("Fixed value")
      assert("Fixed value" === mockedHello.sayHello("Mike"))
    }
  }
}

例2:verify()

import org.scalatest.funspec.AnyFunSpec
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito.{verify, times, atLeastOnce, atLeast, atMost, never}

class Hello {
  def sayHello(name: String): String = {
    s"Hello, ${name}"
  }
}

// MockitoSugar trait provides some basic syntax sugar for Mockito.
class HelloTest extends AnyFunSpec with MockitoSugar {
  describe("For Hello World Test") {
    it ("Hello World For Mockito") {
      // First, create the mock object (mock オブジェクト作成用メソッド)
      val mockedHello = mock[Hello]
  
      mockedHello.sayHello("Mike")
      mockedHello.sayHello("Tom")
      mockedHello.sayHello("Tom")
      mockedHello.sayHello("Tom")

      // verify: メソッド呼び出しの妥当性検証
      verify(mockedHello, atLeastOnce()).sayHello("Mike")
      verify(mockedHello, atLeast(2)).sayHello("Tom")
      verify(mockedHello, atMost(2)).sayHello("Mike")
      verify(mockedHello, times(3)).sayHello("Tom")
      // verify(mockedHello, times(4)).sayHello("Tom") // !!Error!!
      verify(mockedHello, never()).sayHello("Sam")
    }
  }
}

補足:公式ドキュメントのサンプル

公式ドキュメントのサンプル

https://www.scalatest.org/plus/mockito

だが、さっぱり分からなかったが、
どうやら、Githubにあるテストコードの切れ端の模様。

https://github.com/scalatest/scalatestplus-mockito/blob/main/src/test/scala/org/scalatestplus/mockito/MockitoSugarSpec.scala

import もないのにわからんよ、、、

参考文献

https://takezoe.hatenablog.com/entry/2020/11/03/004143
https://inside.pixiv.blog/2022/11/24/120000
http://tkawachi.github.io/blog/2013/08/26/mockito-scala/
https://www.playframework.com/documentation/ja/2.4.x/ScalaTestingWithScalaTest

関連記事

ScalaTest ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/27/001306
ScalaTest ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2023/03/28/003906
ScalaTest ~ with ScalaCheck ~
https://dk521123.hatenablog.com/entry/2023/03/29/000014
Scala ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/03/10/193805
Scala ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/12/184331
Scala ~ 基本編 / 繰り返し ~
https://dk521123.hatenablog.com/entry/2023/01/24/000000
Scala ~ 基本編 / Option型 ~
https://dk521123.hatenablog.com/entry/2023/03/09/000000
Scala ~ 基本編 / メソッド ~
https://dk521123.hatenablog.com/entry/2023/03/03/000000
Scala ~ 基本編 / クラス ~
https://dk521123.hatenablog.com/entry/2023/03/14/000857
Scala ~ 基本編 / コレクション ~
https://dk521123.hatenablog.com/entry/2023/03/13/000345
Scala ~ 基本編 / 日付・日時 ~
https://dk521123.hatenablog.com/entry/2023/03/08/000000
Scala ~ 基本編 / 正規表現
https://dk521123.hatenablog.com/entry/2023/03/18/034704
Scala ~ 基本編 / ジェネリック
https://dk521123.hatenablog.com/entry/2023/03/21/003817
ScalaEnum
https://dk521123.hatenablog.com/entry/2023/01/05/000000
Scala ~ ファイル名・パスの扱い ~
https://dk521123.hatenablog.com/entry/2023/03/11/000000
Scala ~ ファイルハンドリング ~
https://dk521123.hatenablog.com/entry/2023/01/03/000000
ScalaYAML
https://dk521123.hatenablog.com/entry/2023/03/16/012034
ScalaJDBC / DB接続 ~
https://dk521123.hatenablog.com/entry/2023/03/26/000950
ScalaAWS SDK
https://dk521123.hatenablog.com/entry/2023/03/24/211033
SBT ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/22/000000
SBT ~ 基本編 / build.sbt ~
https://dk521123.hatenablog.com/entry/2023/01/27/000000
SBT ~ 基本編 / sbtコマンド ~
https://dk521123.hatenablog.com/entry/2023/01/26/000000
SBT ~ sbtプラグイン
https://dk521123.hatenablog.com/entry/2023/01/25/000000
JavaでEmail ~ JavaMail / Text ~
https://dk521123.hatenablog.com/entry/2016/07/16/222422
JavaでEmail ~ JavaMail / 添付ファイル ~
https://dk521123.hatenablog.com/entry/2016/07/17/023459
JavaでEmail ~ SMTP認証 ~
https://dk521123.hatenablog.com/entry/2016/11/07/215251
JavaでEmail ~ SMTP認証 / DIGEST-MD5
https://dk521123.hatenablog.com/entry/2016/12/07/222229
JavaでEmail ~ JavaMail / TLS
https://dk521123.hatenablog.com/entry/2017/05/03/163219
JavaでEmail ~ JavaMail / Return-Path・Errors-To ~
https://dk521123.hatenablog.com/entry/2017/05/07/000344
Amazon SES ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2017/04/28/234103
Amazon S3 ~ Boto3編 ~
https://dk521123.hatenablog.com/entry/2019/10/21/230004
Amazon S3 ~ Boto3でファイル存在チェック ~
https://dk521123.hatenablog.com/entry/2022/02/26/182526
AWS Glue ~ Scalaでの実装 ~
https://dk521123.hatenablog.com/entry/2023/03/17/000000
AWS Glue ~ ローカル環境を作成する / Glue v3.0版 ~
https://dk521123.hatenablog.com/entry/2022/01/31/165650
LocalStack ~ ローカルで疑似AWSを作成する ~
https://dk521123.hatenablog.com/entry/2019/12/14/010524
LocalStack ~ ローカルで疑似Lambda/S3/DynamoDBを作成する ~
https://dk521123.hatenablog.com/entry/2019/12/16/231149
SparkからSnowflakeへの接続について考える
https://dk521123.hatenablog.com/entry/2023/03/19/013833
Mockito ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2014/07/18/233904
Mockito ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2014/07/19/121409
EasyMock ~入門編~
https://dk521123.hatenablog.com/entry/2010/01/28/234207
WSL ~ Windows Terminal ~
https://dk521123.hatenablog.com/entry/2020/12/10/094125

【Power Apps】Power Apps ~ 基本編 / Collection ~

■ はじめに

https://dk521123.hatenablog.com/entry/2023/02/05/000322
https://dk521123.hatenablog.com/entry/2023/02/06/144231
https://dk521123.hatenablog.com/entry/2023/02/07/001115

の続き。

今回は、Power Appsにおいて、
配列・リストに当たるCollection の操作を調べてみた。

目次

【1】コレクションの作成
 1)ClearCollect
【2】データ編集
 1)Update関数
 2)UpdateIf関数
 3)Patch 関数
【3】レコード削除
 1)Clear関数
 2)Remove関数
【4】繰り返し処理
 1)ForAll関数
【5】絞り込み・検索
 1)Filter関数
 2)LookUp関数
 3)Search関数
【6】ソート処理
 1)SortByColumns関数
【7】列操作
 1)AddColumns関数
 2)RenameColumns関数
 3)ShowColumns関数
 4)DropColumns関数
【8】カウント
 1)Count 関数
 2)CountA 関数
 3)CountIf 関数
 4)CountRows 関数

【1】コレクションの作成

1)ClearCollect

* 持っているデータをクリアして格納

// Tree viewにあるAppを選択して、OnStartプロパティに設定する
ClearCollect(Members,
  {id: "01", name: "Mike", position: "Senior engineer", team: ""},
  {id: "02", name: "Tom", position: "Junior engineer", team: ""},
  {id: "03", name: "Kevin", position: "Senior engineer", team: ""},
  {id: "04", name: "Sam", position: "Junior engineer", team: ""},
  {id: "05", name: "James", position: "Junior engineer", team: ""},
  {id: "06", name: "Benjamin", position: "Senior engineer", team: ""}
);

【2】データ編集

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-update-updateif

1)Update関数

* 指定したレコードの値を修正する

構文

Update( データソース, 置換前レコード, 置換後レコード [, All ] )

2)UpdateIf関数

* 指定した条件でレコードの値を修正する

UpdateIf(Members, position="Junior engineer", {team: "A"})

3)Patch 関数




Patch(Members, First(Filter(Members, position="Junior engineer")), {team: "B"})

【3】レコード削除

1)Clear関数

* コレクションのレコードを全て削除

2)Remove関数

* 指定したひとつのレコードを削除

【4】繰り返し処理

1)ForAll関数

構文

ForAll( データソース , 繰り返す処理)

【5】絞り込み・検索

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-filter-lookup

1)Filter関数

* 指定した条件に合う列を全て返す

構文

Filter(テーブル,条件式1 [,条件式2, ... ] )

テーブル:検索対象のテーブル
条件式:検索対象を指定する条件式

ClearCollect(Members,
 Filter(Members, ThisRecord.position = "Senior engineer"))

2)LookUp関数

* 指定した条件の最初の列を返す
* Excelの VLOOKUP関数と同じような

https://dk521123.hatenablog.com/entry/2012/12/05/020616

構文

LookUp(テーブル, 条件式 [, 内部計算式 ] 

3)Search関数

* 指定した検索語句が含まれる列を返す

構文




【6】ソート処理

* 並び替え処理

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-sort

1)SortByColumns関数

* 1 つまたは複数の列を並べ替える

https://qiita.com/sheenbanach/items/b5bafebd6851986faf94

ClearCollect(Members,
  SortByColumns(Members, "position", SortOrder.Descending))

【7】列操作

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-table-shaping

1)AddColumns関数

ClearCollect(Members, AddColumns(Members, "team", "-"))

2)RenameColumns関数

3)ShowColumns関数

4)DropColumns関数

【8】カウント

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-table-counts

1)Count 関数

* 単一列テーブルのレコードのうち、
 数値が含まれるレコード数をカウント

構文

// SingleColumnTable - 必須。 カウントするレコードの列
Count( SingleColumnTable )

2)CountA 関数

* 単一列テーブルのレコードのうち、
 空白でないレコード数をカウント
 => 空 のテキスト ("") もカウント

構文

// SingleColumnTable - 必須。 カウントするレコードの列
CountA( SingleColumnTable )

3)CountIf 関数

* テーブルのレコードのうち、
 論理式で true になるレコード数をカウント

構文

// Table - 必須。 カウントするレコードのテーブル
// LogicalFormula - 必須。 テーブルの各レコードについて評価する数式
CountIf( Table, LogicalFormula )

4)CountRows 関数

* テーブルのレコード数をカウント

構文

// Table - 必須。 カウントするレコードのテーブル
CountRows( Table )

参考文献

https://www.cloudtimes.jp/dynamics365/blog/power-apps-collection
https://www.geekfujiwara.com/tech/1138/

関連記事

Power Apps ~ 基礎知識編 ~
https://dk521123.hatenablog.com/entry/2023/02/05/000322
Power Apps ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/02/06/144231
Power Apps ~ 基本編 / コンポーネント
https://dk521123.hatenablog.com/entry/2023/02/07/001115
VLOOKUP関数 / TEXT関数
https://dk521123.hatenablog.com/entry/2012/12/05/020616