2018年7月22日 星期日

[Java] AWS Lambda + DynamoDB

承上文 [Java] AWS Lambda 入門, 這次添加存取 DynamoDB。

首先建立一個 DynamoDB 表單 "Employee",同時建立一筆資料。

接下來進入 IAM 建立一個 Role,這裡直接選擇 "AWS service", 在 "Choose the service that will use this role" 選擇 "Lambda", 然後就可以點擊按鈕 "Next: Permissions";
在 "Attach permissions policies" 裡面, 勾選 "AmazonDynamoDBFullAccess","AWSLambdaDynamoDBExecutionRole", 然後點擊按鈕 "Next: Review";
輸入 Role name,就可以建立一個 Role 了。

接下來回到之前建立的 Lambda,往下拉到 "Execution role",選取剛剛建立的 Role 就成功了。

可以參考官方文件用 node 簡單測試一下,

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region 
AWS.config.update({region: 'ap-northeast-1'});

exports.handler = (event, context, callback) => {
  // Create the DynamoDB service object
  ddb = new AWS.DynamoDB({apiVersion: '2012-10-08'});

  var params = {
    TableName: 'Employee',
    Key: {
      'EmpId' : {S: '1000'}
    }
    //, ProjectionExpression: 'EmpId'
  };

  // Call DynamoDB to read the item from the table
  ddb.getItem(params, function(err, data) {
    if (err) {
      console.log("Error", err);
    } else {
      console.log("Success", data.Item);
      callback(null, data);
    }
  });
};

回到之前的 Java Project,修改一下:

package example;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class Hello implements RequestHandler {
    @Override
    public String handleRequest(Integer input, Context context) {
        AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
        DynamoDB dynamoDB = new DynamoDB(client);
        Table table = dynamoDB.getTable("Employee");
        Item item = table.getItem("EmpId", "10000");
        System.out.println(item.toJSONPretty());
        return item.toJSON();
    }
}

另外 Lambda 也提供直接轉換物件的方式,我們不用再去處理 Json 映射物件的問題。

package example;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import lombok.Data;

public class Hello implements RequestHandler {
    @Override
    public String handleRequest(Integer input, Context context) {
        AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
        DynamoDBMapper dynamodbMapper = new DynamoDBMapper(client);
        Employee result = dynamodbMapper.load(Employee.class, "10000");
        System.out.println(result);
        return result.toString();
    }

    @Data
    @DynamoDBTable(tableName = "Employee")
    class Employee {
        @DynamoDBHashKey(attributeName = "EmpId")
        private String id;
        @DynamoDBAttribute(attributeName = "Name")
        private String name;
    }
}

系列:

[Java] AWS Lambda 入門

首先修改 build.gradle。

// build.gradle
apply plugin: 'java'
repositories {
    mavenCentral()
}
dependencies {
    compile (
        'com.amazonaws:aws-lambda-java-core:1.1.0',
        'com.amazonaws:aws-lambda-java-events:1.1.0'
    )
}
task buildZip(type: Zip) {
    from compileJava
    from processResources
    into('lib') {
        from configurations.compile.Classpath
    }
}
build.dependsOn buildZip
如果打包有發生錯誤,可以刪除 .Classpath 再重新打包嘗試。

接下來寫一隻簡單的範例。

package example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class Hello implements RequestHandler<Integer, String> {
    public String myHandler(int myCount, Context context) {
        return String.valueOf(myCount);
    }
    @Override
    public String handleRequest(Integer input, Context context) {
        return "Default " + input.toString();
    }
}

打包後上傳 .jar,"Handler" 改成 "example.Hello::myHandler", 然後在 "Configure test events" 裡面將輸入值改成數字(目前預設是 Json),就可以測試了。

"Handler" 的格式是 "包.類::函數";所以上述可以改成 "example.Hello:: handleRequest" 再測試一次。 另外如果輸入格式是 Json,可以將 Integer 改成 Map<String,String>

備註:Java 預設必須調整記憶體大於 768MB 與 Timeout 5秒,否則都會出現 "Task timed out after 3.00 seconds"。 因為實際執行的日誌顯示最大使用記憶體只達 34MB,推測啟動服務加上載入 .jar 需要這麼多才可以。

參考資料:

系列:

2018年7月9日 星期一

[Eclipse] 使用 Lombok

承上文 [Android Studio] 使用 LombokLombok 是在 POJO 寫些注解,然後在編譯時期幫忙產生冗余代碼。

在 build.gradle 設置:

dependencies {
    ...
    compile 'org.projectlombok:lombok:1.18.+'
}

下載 lombok.jar 放置在 /Applications/Eclipse.app/Contents/MacOS, 在 /Applications/Eclipse.app/Contents/Eclipse/eclipse.ini 後面增加一行:
-javaagent:lombok.jar

參考資料:https://projectlombok.org/setup/android
參考資料:Install Lombok for Eclipse on Mac

Eclipse IDE for Java Developers

Version: Oxygen Release (4.7.0)
Build id: 20170620-1800

2018年5月31日 星期四

[Java] 使用另一個列表進行排序

class Foo {
    private String id;
    public Foo(String id) { this.id = id; }
    public String getId() { return id; }
    @Override
    public String toString() { return id; }
}

List<Foo> list = Arrays.asList(new Foo("1"), new Foo("2"), new Foo("3"), new Foo("4"));
List<String> keys = Arrays.asList("2","1","3","4");

// before Java 8
List<Foo> result1 = new ArrayList<>(list);
Collections.sort(result1, new Comparator() {
    @Override
    public int compare(Foo o1, Foo o2) {
        return keys.indexOf(o1.getId()) < keys.indexOf(o2.getId()) ? -1 : 1;
    }
});

// after Java 8
List<Foo> result2 = list.stream()
    .sorted(Comparator.comparing(e -> keys.indexOf(e.getId())))
    .collect(Collectors.toList());

System.out.println(result1); // [2, 1, 3, 4]
System.out.println(result2); // [2, 1, 3, 4]

2018年5月28日 星期一

[PHP] unique values & resort keys

function calc($t0, $t1)
{
    $v0 = array_sum(explode(' ', $t0));
    $v1 = array_sum(explode(' ', $t1));
    $sub = $v1 - $v0;
    echo "$v1 - $v0 = $sub\n";
    return $sub;
}

$a = [116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,117,116,117,116,116,116,116,116,119];

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_values(array_unique($a));
}
$t1 = microtime();
$method1 = calc($t0, $t1);     // output: 1527477436.7125 - 1527477409.434 = 27.278562068939

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_keys(array_flip($a));
}
$t1 = microtime();
$method2 = calc($t0, $t1);     // output: 1527477439.6214 - 1527477436.7126 = 2.908814907074

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_merge(array_flip(array_flip($a)));
}
$t1 = microtime();
$method3 = calc($t0, $t1);     // output: 1527477443.3256 - 1527477439.6215 = 3.7041912078857

有時候直覺的方式會比較慢。

參考資料:http://php.net/manual/en/function.array-unique.php#70786

PHP 5.6.36 (cli) (built: May  9 2018 20:31:47)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Xdebug v2.3.3, Copyright (c) 2002-2015, by Derick Rethans

2018年5月8日 星期二

[Java] find enum with Java 8 streams

import java.util.EnumSet;

public enum Status {
   PENDING("pending"),
   ACTIVE("active");

   public static final Status fromString(String status) {
      return Stream.of(Status.values)
         .filter(e -> e.status.equals(status))
         .findAny();
         .orElseThrow(() -> new IllegalArgumentException("No enum constant " + status));
   }

   private final String status;

   private Status(String status) {
      this.status = status;
   }

   @Override
   public String toString() {
      return status;
   }

}

參考資料:

[Java] running JAX-RS with gretty

開一個專案叫 jersey,然後按下面加入代碼:

// build.gradle
plugins {
    id 'war'
    //id 'org.gretty' version '2.1.0'
    id 'org.akhikhl.gretty' version '2.0.0'
}
// 'jetty7', 'jetty8', 'jetty9', 'jetty9.3', 'jetty9.4', 'tomcat7', 'tomcat8'
//gretty.servletContainer='jetty9'
repositories {
    jcenter()
}
dependencies {
    compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.26'
    compile 'org.glassfish.jersey.inject:jersey-hk2:2.27'
}
// src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID"
   version="3.0">
   <display-name>My JAX-RS</display-name>
   <servlet>
      <servlet-name>jersey-servlet</servlet-name>
      <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
      <init-param>
         <param-name>jersey.config.server.provider.packages</param-name>
         <param-value>demo</param-value>
      </init-param>
   </servlet>
   <servlet-mapping>
      <servlet-name>jersey-servlet</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>
</web-app>
// src/main/java/demo/Hello.java
package demo;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

@Path("/hello")
public class Hello {
    @GET()
    public String hello() {
        return "hello";
    }

    @GET
    @Path("/{name}")
    public Response getMessage(@PathParam("name") String name) {
        String outMsg = "Hello " + name + "!";
        return Response.status(200).entity(outMsg).build();
    }
}

接下來執行 gradle appRun

$ gradle appRun
:prepareInplaceWebAppFolder UP-TO-DATE
:createInplaceWebAppFolder UP-TO-DATE
:compileJava
:processResources NO-SOURCE
:classes
:prepareInplaceWebAppClasses
:prepareInplaceWebApp
:appRun
19:46:58 INFO  Jetty 9.2.24.v20180105 started and listening on port 8080
19:46:58 INFO  JAX-RS Application Demo runs at:
19:46:58 INFO    http://localhost:8080/jersey

> Task :appRun
Press any key to stop the server.
<===========--> 87% EXECUTING [17s]
> :appRun

然後就可以測試了:

  • http://localhost:8080/jersey/hello
  • http://localhost:8080/jersey/hello/John

備註:

  • web.xml 的 "param-value" 必須跟套件名一樣,jersey 才會作用。
  • 用 gretty 試跑的時候,URL 是帶上專案名稱,像這裡是 "/jersey"。放在 Servlet 上面跑的時候則是看檔案名稱,例如 "jersey.war";如果不想要這個路徑,可以改成 "ROOT.war"。
  • web.xml 預設路徑是 src/main/webapp/WEB-INF,但是可以在 Gradle 的 war plugin 設定。

備註2:Servlet 3 之後可以不寫 web.xml,改成繼承 ResourceConfig。

// src/main/java/demo/MyApplication.java
package demo;

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/")
public class MyApplication extends ResourceConfig {
    public MyApplication() {
        packages("demo");
    }
}

備註3:如果 gretty 發生 Java9 相容問題,

Caused by: java.lang.RuntimeException: Error scanning entry META-INF/versions/9/module-info.class from jar file: .gradle/caches/modules-2/files-2.1/...

Caused by: java.lang.RuntimeException: Error scanning entry module-info.class from jar file: .gradle/caches/modules-2/files-2.1/...
可以設定參數 gretty.servletContainer 為 "jetty9.4" 或是 "tomcat8" 測試。

參考資料:

[Java] Invalid HTTP method: PATCH

最近系統需要使用 Netty4,所以把衝突的 Netty3 拆掉,然後就出現了例外。 pom.xml <dependency> <groupId>com.ning</groupId> <artifactId>as...