リスト要素の前方を参照する処理(1)

特別に新しいことではない、Java標準で 1.6の時代からあることではあるが、
意外と正確に迅速に書けないロジック
(課題)A, B. C,.... と文字列のリストが存在する。
    これを、前の要素を参照しながら抽出処理する。

ListIterator の hasPrevious() と previous() を使う。
注意しなければならないのは、previous() は、参照ポインタが1つ前に移動するので、
進めるには2回 next() を実行することだ。

List<String> list = List.of("A", "B", "C");

for(ListIterator<String> it = list.listIterator(); it.hasNext();) {
    String pre = null;
    if (it.hasPrevious()) {
        pre = it.previous();
        it.next();
    }
    String cur = it.next();
    // TODO 前の要素を認識した処理
    System.out.println("cur="+cur+"  pre="+pre);
}

結果

cur=A  pre=null
cur=B  pre=A
cur=C  pre=B

汎用的にする手段として、
java.util.AbstractMap.SimpleEntry の key, value に置き換えて value を前の要素する。

public static <T> List<SimpleEntry<T, T>> previousReferList(List<T> list){
    List<SimpleEntry<T, T>> rtn = new ArrayList<>();
    for(ListIterator<T> it = list.listIterator(); it.hasNext();) {
        T pre = null;
        if (it.hasPrevious()) {
            pre = it.previous();
            it.next();
        }
        rtn.add(new SimpleEntry<T, T>(it.next(), pre));
    }
    return rtn;
}
public static <T> List<SimpleEntry<T, T>> previousReferList(T...t){
    return previousReferList(Arrays.asList(t));
}
public static <T> List<SimpleEntry<T, T>> previousReferLists(T[] t){
    return previousReferList(Arrays.asList(t));
}

実行

List<String> list = List.of("A", "B", "C");

List<SimpleEntry<String, String>> res = previousReferList(list);
res.stream().forEach(e->{
    // TODO 前の要素は、e.getValue() 
    System.out.println("cur="+e.getKey()+"  pre="+e.getValue());
});

結果

cur=A  pre=null
cur=B  pre=A
cur=C  pre=B

oboe2uran.hatenablog.com

Jackson Map デシリアライズ

JSON → Map

import com.fasterxml.jackson.core.type.TypeReference;

TypeReference を使って

Map<String, Object> map = new ObjectMapper()
   .readValue(jsontxt, new TypeReference<LinkedHashMap<String, Object>>(){});
try(InputStream inst = new FileInputStream(Thread.currentThread().getContextClassLoader()
.getResource("./application.json").getPath())){
     Map<String, Object> map = new ObjectMapper().readValue(inst, new TypeReference<LinkedHashMap<String, Object>>(){});
}catch(IOException e) {
      e.printStackTrace();
}

AWS Lambda関数 Java イベントの認識

AWS Lambda関数 をJavaで構築する場合、イベントの認識はハンドラメソッドのガイドとして
次がある。
docs.aws.amazon.com

いきなり Map<String, String> で受け取るハンドラメソッドの方法が書いてあります。
JSONなら、JSONとして受け取ったら直ぐに parse して処理したいものです。

受け取るJSON を任意のクラスで定義して変換させるには、InputStream RequestHandler を実装します。
例)

public class SampleHandler implements RequestHandler<InputStream, String>{

    @Override
    public String handleRequest(InputStream inst, Context context){
        ObjectMapper mapper = new ObjectMapper();
        try{
            SampleDto dto = mapper.readValue(inst, SampleDto.class);

        }catch(IOException e){
            // TODO
        }
        return "";
   }  
}

AWS SDK が、Jackson を依存関係に持っているのでこれを使います。
もしも、受け取るJSON に、日付を表現する値がありこれを java.time.LocalDate に変換したいのであれば、
Maven pom.xml に以下を追加して

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
     <version>2.13.1</version>
</dependency>

JavaTimeModule を使って、日付 JSON のデシリアライズ定義を加えておくと良いでしょう。

public class SampleHandler implements RequestHandler<InputStream, String>{

    @Override
    public String handleRequest(InputStream inst, Context context){
        // 日付 JSON のデシリアライズ定義
        JavaTimeModule jtm = new JavaTimeModule();
        jtm.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        ObjectMapper mapper = new ObjectMapper().registerModule(jtm);
        try{
            SampleDto dto = mapper.readValue(inst, SampleDto.class);

        }catch(IOException e){
            // TODO
        }
        return "{}";
   }
}   

AWS EventBridge で、Javaで書いた Batch Job にパラメータを渡す。

AWS のナレッジを参考にするしかない。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/batch-parameters-trigger-eventbridge/

「準備:Job定義」
Javaで書いた Batch のイメージ作成済でAWS Job定義のジョブ設定を行う。
f:id:posturan:20220306125009j:plain
”Ref””::"{パラメータ参照名}
で、実行するコマンド、java -jar JARファイル名 に、パラメータを追加します。
Job定義で、パラメータを設定する場合は、この Ref:: の記述に合わせて、
f:id:posturan:20220306125521j:plain
のように設定します。
手動でJob生成&送信する時でもこのパラメータ設定&送信できます。

AWS EventBridge でイベント実行のJob送信で、、、
f:id:posturan:20220306125819j:plain
AWS EventBridge のルールで新しくイベントパターンをスケジュールで作成します。
ターゲットを選択で、「バッチジョブのキュー」を選択して対象のJobキュー、Job定義、Job名を指定
ターゲット入力を設定をクリックして
定数(JSONテキスト)で、パラメータとコマンドを入力設定します。
1行の入力フォームなので、長い場合この AWSコンソールページで設定するのは辛いです。
隠れてしまっているので、全部記述すると

{ "Parameters": {"param1":"ABC","param2":"1024" }, "ContainerOverrides": { "Command": ["java","-jar","sample.jar","Ref::param1","Ref::param2"] } }

ということになります。
パラメータの値として、JSON を渡す場合など、値に (")ダブルクォーテーションを含んでしまう場合は、
\ 文字(逆スラッシュ文字)でエスケープしなければなりません。

ローカルの置いた Jar をMavenでビルドの依存関係に追加する。

Maven セントラルリポジトリにない JAR 、あるいは外部サーバーのMavenリポジトリから取得できない
JARをローカルPCに置いて、Maven でビルドの依存関係に追加する方法

Maven<build> <plugins> に、以下のように<plugin> を挿入すれば可能になる。

例)poisample-0.0.1-SNAPSHOT.jar が、プロジェクト内 /lib の下に配置されているとする。
JARは、
groupId : org.poisample
artifactId : poisample
version : 0.0.1-SNAPSHOT

とする。
追加する plugin

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-install-plugin</artifactId>
   <inherited>false</inherited>
   <version>2.5.2</version>
   <executions>
      <execution>
         <id>poisample</id>
         <phase>clean</phase>
         <goals>
             <goal>install-file</goal>
         </goals>
         <configuration>
            <file>${basedir}/lib/poisample-0.0.1-SNAPSHOT.jar</file>
            <groupId>org.poisample</groupId>
            <artifactId>poisample</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <packaging>jar</packaging>
         </configuration>
      </execution>
   </executions>
</plugin>

使用するために、

<dependency>
  <groupId>org.poisample</groupId>
  <artifactId>poisample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

pom.xml をこのように更新したら、Maven clean  を実行して
Maven プロジェクトの更新を行えばよい。

AWS boto3 の Java版?!

AWS boto3 に触れたので、これと同じことをJava でやるには、、、
oboe2uran.hatenablog.com

aws-java-sdk-batch
aws.amazon.com

よりも、、software.amazon.awssdk
AWS SDK for Java 2.x の方が、インターフェースもそっくり。

sdk.amazonaws.com

DescribeJobsRequest (AWS SDK for Java - 2.17.137)

<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>services</artifactId>
  <version>2.17.137</version>
</dependency>
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>core</artifactId>
  <version>2.17.137</version>
</dependency>

AWS boto3 submit_job で実行する Batch Job のステータスを監視する

Batch — Boto3 Docs 1.21.3 documentation
のドキュメントを頼りに、以下のような 1つの Job が動いて成功したら次の Job を実行する
LambdaPythonで書く。

lambda_function.py ソースコード

import logging
import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    logger.info('# boto3 vertion: {0}'.format(boto3.__version__));
    
    client = boto3.client('batch', region_name='ap-northeast-1')

    JobAsubmit = client.submit_job(
        jobName = 'JOB-A',
        jobQueue = 'job-queue',
        jobDefinition = 'JOB-A:1',
        containerOverrides={
            'command': ['java', '-jar', 'ajob.jar' ]
        }
    )
    JobBsubmit = client.submit_job(
        jobName = 'JOB-B',
        jobQueue = 'job-queue',
        dependsOn=[
            {
                'jobId': JobAsubmit['jobId']
            },
        ],
        jobDefinition = 'JOB-B:1',
        containerOverrides={
            'command': ['java', '-jar', 'bjob.jar' ]
        }
    )
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

JOB-A, JOB-B の実行状況を参照しようとする。

docs.aws.amazon.com

によると、describe_jobs(**kwargs) を使用するのだが、
引数に与えるのは、Job定義ではなく、発行したJobId である。
しかも、100個までしか指定できない。

だから、describe_jobs 呼び出しは以下のようにする。

describeResonse = client.describe_jobs(jobs=[ JobAsubmit['jobId'], JobBsubmit['jobId'] ])

# 参照できた jobName と status を、'jobName:status’の書式にしてリスト化する。
list = [ "{jobName}:{status}".format(**job) for job in describeResonse['jobs'] ]

このままでは、describe_jobs 実行に対して、AccessDenied が発生する
https://aws.amazon.com/jp/premiumsupport/knowledge-center/troubleshoot-iam-policy-issues/

と案内あるが、
すぐに解決に辿り着けない。非常に解りにくい。

結局、手っ取り早く、実行する Lambda インラインポリシーを作成して以下を付与して解決

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "batch:DescribeJobs"
            ],
            "Resource": "*"
        }
    ]
}