ngtokuの日記

主に雑記帳です。SNSではngtokuのID取れなかったんで、別のIDでやってます。

備忘メモ(React.js環 開発境セットアップ)

マシン入れ替えたので備忘録兼作業メモ

作業の流れ

  1. VSCode インストール
  2. Homebrew インストール
  3. anyenv & nodenv インストール
  4. nodenv-default-packages 設定
  5. nodenv 個別バージョンインストール
  6. create-react-app 実行

VSCode インストール

普通にここから。Visual Studio Code – コード エディター | Microsoft Azure

Homebrew インストール

これまんまで。macOS(またはLinux)用パッケージマネージャー — Homebrew

anyenv & nodenv インストール

Big Surなのでzshのはずだけど念のため確認。

$ echo $SHELL
/bin/zsh

確認できたのでインストール。

$ brew install anyenv
$ echo 'eval "$(anyenv init -)"' >> ~/.zshrc
$ anyenv install --init
$ exec $SHELL -l
$ anyenv install nodenv
$ exec $SHELL -l

nodenv-default-packages インストール

$ mkdir -p $(anyenv root)/plugins
$ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
$ mkdir -p "$(nodenv root)"/plugins
$ git clone https://github.com/nodenv/nodenv-default-packages.git "$(nodenv root)/plugins/nodenv-default-packages"

nodenvのdefault-packages は以下。

$ vi $(nodenv root)/default-packages
yarn
typescript
ts-node
typesync

nodenv 個別バージョンインストール

14.5で挙動変わるから、初心者は14.4.0入れとけって言われたので今もそれに倣っているが、そろそろ最新版にしてもいい気がする。

$ nodenv install -l
$ nodenv install 14.4.0
$ nodenv global 14.4.0

create-react-app 実行

作業フォルダ作ってテストプロジェクト作成。

$ mkdir -p work/ReactJS
$ cd work/ReactJS
$ npx create-react-app hello-world --template typescript
$ cd hello-world
$ yarn start

備忘メモ(Vagrant + VirtualBox + Docker + VisualStudioCode on Mac)

そのうち手順を整理することになりそうなのでメモ。

参考

Vagrantを使う「Mac最速のDocker環境」を初心者向けに解説【遅いMac for Dockerを卒業】 - Qiita

Vagrant+VirtualBoxでUbuntu環境構築 - Qiita

Vagrantバージョン

Big Surだとバージョン2.2.14はvagrant upがコケるので、2.2.13に変更。 コケない場合は作業不要。

Getting 'no implicit conversion of nil into String' from hostsupdater · Issue #2112 · geerlingguy/drupal-vm · GitHub

ダウングレードは以下参考。二行目のversionと三行目のsha256 だけ変更。

Downgrade homebrew (cask) package · GitHub

$ brew edit vagrant --cask
cask "vagrant" do
  version "2.2.13"
  sha256 "b65adb59bef5b69be61bd79a34e3532533a172491b307718d1c4a5a46209e81f"

  url "https://releases.hashicorp.com/vagrant/#{version}/vagrant_#{version}_x86_64.dmg",
      verified: "hashicorp.com/vagrant/"
  appcast "https://github.com/hashicorp/vagrant/releases.atom"
  name "Vagrant"
  desc "Development environment"
  homepage "https://www.vagrantup.com/"

  pkg "vagrant.pkg"

  uninstall script:  {
    executable: "uninstall.tool",
    input:      ["Yes"],
    sudo:       true,
  },
  pkgutil: "com.vagrant.vagrant"

  zap trash: "~/.vagrant.d"
end

作業の流れ

  1. Homebrewインストール
  2. VirtualBoxインストール
  3. Vagrantインストール
  4. Vagrantプラグインインストール
  5. Vagrant設定
  6. Mutagenインストール
  7. Mutagen設定
  8. docker-composer作成
  9. 確認用PHP作成
  10. ssh config作成(無い場合)
  11. Vagrant起動
  12. Vagrant接続(terminal)
  13. Docker起動
  14. アクセス確認
  15. Docker停止
  16. Vagrant切断
  17. Vagrant接続(Visual Studion Code)
  18. Vagrant停止

Homebrewインストール

これまんまで。macOS(またはLinux)用パッケージマネージャー — Homebrew

VirtualBoxインストール

$ brew install --cask virtualbox

セキュリティとプライバシーの設定でコケたら設定してやり直し。 設定したら再起動をする必要があるので注意。

Vagrantインストール

$ brew install --cask vagrant

Vagrantプラグインインストール

$ vagrant plugin install vagrant-disksize vagrant-hostmanager vagrant-mutagen vagrant-docker-compose

Vagrant設定

Box調査。Discover Vagrant Boxes - Vagrant Cloud 今回はubuntu/xenial64を選択。

作業フォルダ作成&移動。以後はcdしない限りここにずっと居て、各種ディレクトリ、ファイルもここに作成。 おっと、Macだとフォルダだったかな。

$ mkdir  ~/Documents/Vagrant
$ cd ~/Documents/Vagrant

Vagrantfile作成。

$ vagrant init ubuntu/xenial64

編集

$ vi Vagrantfile

Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/xenial64"
    config.vm.hostname = "vagrant-ubuntu"
    config.vm.network "private_network", ip: "192.168.33.10"
    config.vm.provider "virtualbox" do |vb|
        vb.gui = false
        vb.cpu = 1
        vb.memory = "2048"
        vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
        vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
    end
    config.disksize.size = '10GB'
    config.mutagen.orchestrate = true
    config.vm.synced_folder './', '/home/vagrant/app', type: "rsync",
        rsync_auto: true,
        rsync__exclude: ['.git/', 'node_modules/', 'log/', 'tmp/']
    config.vm.provision :docker, run: 'always'
    config.vm.provision :docker_compose
end

Mutagenインストール

brew install mutagen-io/mutagen/mutagen

Mutagen設定

vi mutagen.yml

sync:
  app:
    mode: "two-way-resolved"
    alpha: "./"
    beta: "vagrant-ubuntu:/home/vagrant/app"
    ignore:
      vcs: true
      paths:
        - "/node_modules"
        - "/log"
        - "/tmp"

docker-composer作成

$ vi docker-composer.yml
version: '3'
services:
  web:
    image: php:7.4-apache-buster
    ports:
      - "80:80"
    volumes:
      - ./public:/var/www/html:cached

確認用PHP作成

$ mkdir public
$ vi public/index.php
 <?php
 phpinfo();
$ vi public/.htaccess
DirectoryIndex index.php
$ chmod -R 755 public                 

ssh config作成(無い場合)

まっさらな環境の場合、ファイルが無いので作る。中身は空でOK。

$ mkdir ~/.ssh
$ touch ~/.ssh/config

Vagrant起動

$ vagrant up

Vagrant接続(terminal)

$ vagrant ssh
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-198-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

1 package can be updated.
1 of these updates is a security update.
To see these additional updates run: apt list --upgradable

New release '18.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Docker起動

vagrant@vagrant-ubuntu:~$ cd app
vagrant@vagrant-ubuntu:~/app$ docker-compose up

アクセス確認

ブラウザで http://192.168.33.10/ にアクセス。 この場合のIPアドレスはVagrantfileで指定したもの。

Docker停止

Ctrl-C

Vagrant切断

vagrant@vagrant-ubuntu:~$ exit

Vagrant接続(Visual Studio Code)

Visual Studio Codeにremote developmentを追加。 ssh configにvagrant用のエントリ追加。

$ vagrant ssh-config  >> ~/.ssh/config 

後は繋ぐのみ。 Macの場合はシステム環境設定のセキュリティとプライバシーでVisual Studio Codeにフォルダのアクセス権を設定してあげる必要があるかも。

Vagrant停止

$ vagrant halt

SAP HANAにoData via olingo(V2) with ベーシック認証でアクセス(POST)

以下はエントリー読むだけだったんで、今度はPOSTでデータ作成。
SAP HANAにoData via olingo(V2) with ベーシック認証でアクセス - ngtokuの日記


以下にあるサンプルコードだとHTTPステータスコードがBad Requestだったんで、試しにデータ本体だけ突っ込んだらレコード作成できました。
my-car-service/OlingoSampleApp.java at master · qinhaizong/my-car-service · GitHub


夜も遅いので中途半端だけど変更メソッドだけログ的に記載。

	  private ODataEntry writeEntity(Edm edm, String absolutUri, String entitySetName, 
	      Map<String, Object> data, String contentType, String httpMethod) 
	      throws EdmException, MalformedURLException, IOException, EntityProviderException, URISyntaxException {

	    HttpURLConnection connection = initializeConnection(absolutUri, contentType, httpMethod);
	    
        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
        // 本当はここをパラメータdataからJsonに変更する。MapでなくてJacksonを使ってBeanから変換したほうがいいかなって
        out.write("{\"ID\":\"GV006\",\"NAME\":\"'NEW DATA TOKU NG2'\",\"GROUP\":\"'POST DATA'\",\"CREATE_DATE\":\"\\/Date(1587911931893)\\/\"}");
        out.close();
        connection.connect();
        final int status = connection.getResponseCode();
        print("response code=" + status);
	    ODataEntry entry = null;
	    EdmEntityContainer entityContainer = edm.getDefaultEntityContainer();
	    EdmEntitySet entitySet = entityContainer.getEntitySet(entitySetName);
        if (status == 201) {
  	      InputStream content = connection.getInputStream();
  	      content = logRawContent(httpMethod + " request on uri '" + absolutUri + "' with content:\n  ", content, "\n");
  	      entry = EntityProvider.readEntry(contentType,
  	          entitySet, content, EntityProviderReadProperties.init().build());
        }

	    connection.disconnect();

	    return entry;
	  }

うぇーい。

SAP HANAにoData via olingo(V2) with ベーシック認証でアクセス

以下で作ったHANAに、テーブル作成。

ngtoku.hatenablog.com

CREATE COLUMN TABLE "SYSTEM"."TOKUTEST"(
    "ID" CHAR(5),
    "NAME" NVARCHAR(50),
    "GROUP" NVARCHAR(50),
    "CREATE_DATE" DATE,
    PRIMARY KEY("ID")
);


データ投入。

insert into "SYSTEM"."TOKUTEST" values('GV001', 'NG TOKU', 'SYSTEM CONSULTING GROUP', CURRENT_DATE  );
insert into "SYSTEM"."TOKUTEST" values('GV002', 'NG TOKU2', 'SYSTEM CONSULTING GROUP', CURRENT_DATE  );
insert into "SYSTEM"."TOKUTEST" values('GV003', 'NG TOKU3', 'SYSTEM CONSULTING GROUP', CURRENT_DATE  );
insert into "SYSTEM"."TOKUTEST" values('GV004', 'NG TOKU4', 'SYSTEM CONSULTING GROUP', CURRENT_DATE  );

sensorjump.xsodata修正。

service { "SYSTEM"."TOKUTEST" as "TOKUTEST"; }


Javaで取得。
基本的に以下にあるサンプルコードのまんまで、最後にベーシック認証のユーザ名とパスワード入れてるだけ。
my-car-service/OlingoSampleApp.java at master · qinhaizong/my-car-service · GitHub


サンプルなのでベタ打ちだけど、URLとかベーシック認証のユーザ名とパスワードなんかはプロパティファイルに外だしが基本になるかと。
URL毎にユーザ名とパスワードが異なる場合はこんな感じで定義して、TOKUTESTはそれ用に用意したユーザ名とパスワード、TOKUTEST2は定義がないからDEFAULTから取ってくるみたいな感じになるんじゃないかと。

DEFAULT_USERNAME  = xxxxxx
DEFAULT_PASSWORD = xxxxxx

TOKUTEST_URL = http://IPアドレス:8090/toku/oData/sensorjump.xsodata
TOKUTEST_USERNAME = yyyyy
TOKUTEST_PASSWORD = yyyyy

TOKUTEST2_URL =  = http://IPアドレス:8090/toku/oData/tokutest2.xsodata


Javaのコードは以下。

package com.servesync.olingo.clienttest;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.edm.EdmEntityContainer;
import org.apache.olingo.odata2.api.edm.EdmEntitySet;
import org.apache.olingo.odata2.api.edm.EdmException;
import org.apache.olingo.odata2.api.ep.EntityProvider;
import org.apache.olingo.odata2.api.ep.EntityProviderException;
import org.apache.olingo.odata2.api.ep.EntityProviderReadProperties;
import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
import org.apache.olingo.odata2.api.ep.entry.ODataEntry;
import org.apache.olingo.odata2.api.ep.feed.ODataDeltaFeed;
import org.apache.olingo.odata2.api.ep.feed.ODataFeed;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.processor.ODataResponse;

public class App {
	  public static final String HTTP_METHOD_PUT = "PUT";
	  public static final String HTTP_METHOD_POST = "POST";
	  public static final String HTTP_METHOD_GET = "GET";
	  private static final String HTTP_METHOD_DELETE = "DELETE";

	  public static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
	  public static final String HTTP_HEADER_ACCEPT = "Accept";

	  public static final String APPLICATION_JSON = "application/json";
	  public static final String APPLICATION_XML = "application/xml";
	  public static final String APPLICATION_ATOM_XML = "application/atom+xml";
	  public static final String APPLICATION_FORM = "application/x-www-form-urlencoded";
	  public static final String METADATA = "$metadata";
	  public static final String INDEX = "/index.jsp";
	  public static final String SEPARATOR = "/";
	  
	  public static final boolean PRINT_RAW_CONTENT = true;

	public App() {
	}

	public static void main(String[] args) throws Exception {
	    App app = new App();

	    String serviceUrl = "http://IPアドレス:8090/toku/oData/sensorjump.xsodata";
	    String usedFormat = APPLICATION_JSON;

	    print("\n----- Generate sample data ------------------------------");

	    print("\n----- Read Edm ------------------------------");
	    Edm edm = app.readEdm(serviceUrl);
	    print("Read default EntityContainer: " + edm.getDefaultEntityContainer().getName());

	    print("\n----- Read Feed ------------------------------");
	    ODataFeed feed = app.readFeed(edm, serviceUrl, usedFormat, "TOKUTEST");
	    
	    print("Read: " + feed.getEntries().size() + " entries: ");
	    for (ODataEntry entry : feed.getEntries()) {
	      print("##########");
	      print("Entry:\n" + prettyPrint(entry));
	      print("##########");
	    }

	    print("\n----- Read Entry ------------------------------");
	    ODataEntry entry = app.readEntry(edm, serviceUrl, usedFormat, "TOKUTEST", "'GV002'");
	    print("Single Entry:\n" + prettyPrint(entry));

	  }

	  private static void print(String content) {
	    System.out.println(content);
	  }

	  private static String prettyPrint(ODataEntry createdEntry) {
	    return prettyPrint(createdEntry.getProperties(), 0);
	  }

	  private static String prettyPrint(Map<String, Object> properties, int level) {
	    StringBuilder b = new StringBuilder();
	    Set<Entry<String, Object>> entries = properties.entrySet();

	    for (Entry<String, Object> entry : entries) {
	      intend(b, level);
	      b.append(entry.getKey()).append(": ");
	      Object value = entry.getValue();
	      if(value instanceof Map) {
	        value = prettyPrint((Map<String, Object>)value, level+1);
	        b.append(value).append("\n");
	      } else if(value instanceof Calendar) {
	        Calendar cal = (Calendar) value;
	        value = SimpleDateFormat.getInstance().format(cal.getTime());
	        b.append(value).append("\n");
	      } else if(value instanceof ODataDeltaFeed) {
	        ODataDeltaFeed feed = (ODataDeltaFeed) value;
	        List<ODataEntry> inlineEntries =  feed.getEntries();
	        b.append("{");
	        for (ODataEntry oDataEntry : inlineEntries) {
	          value = prettyPrint((Map<String, Object>)oDataEntry.getProperties(), level+1);
	          b.append("\n[\n").append(value).append("\n],");
	        }
	        b.deleteCharAt(b.length()-1);
	        intend(b, level);
	        b.append("}\n");
	      } else {
	        b.append(value).append("\n");
	      }
	    }
	    // remove last line break
	    b.deleteCharAt(b.length()-1);
	    return b.toString();
	  }

	  private static void intend(StringBuilder builder, int intendLevel) {
	    for (int i = 0; i < intendLevel; i++) {
	      builder.append("  ");
	    }
	  }

	  public Edm readEdm(String serviceUrl) throws IOException, ODataException {
	    InputStream content = execute(serviceUrl + SEPARATOR + METADATA, APPLICATION_XML, HTTP_METHOD_GET);
	    return EntityProvider.readMetadata(content, false);
	  }

	  public ODataFeed readFeed(Edm edm, String serviceUri, String contentType, String entitySetName)
	      throws IOException, ODataException {
	    EdmEntityContainer entityContainer = edm.getDefaultEntityContainer();
	    String absolutUri = createUri(serviceUri, entitySetName, null);

	    InputStream content = execute(absolutUri, contentType, HTTP_METHOD_GET);
	    return EntityProvider.readFeed(contentType,
	        entityContainer.getEntitySet(entitySetName),
	        content,
	        EntityProviderReadProperties.init().build());
	  }

	  public ODataEntry readEntry(Edm edm, String serviceUri, String contentType, String entitySetName, String keyValue)
	      throws IOException, ODataException {
	    return readEntry(edm, serviceUri, contentType, entitySetName, keyValue, null);
	  }

	  public ODataEntry readEntry(Edm edm, String serviceUri, String contentType, 
	      String entitySetName, String keyValue, String expandRelationName)
	      throws IOException, ODataException {
	    // working with the default entity container
	    EdmEntityContainer entityContainer = edm.getDefaultEntityContainer();
	    // create absolute uri based on service uri, entity set name with its key property value and optional expanded relation name
	    String absolutUri = createUri(serviceUri, entitySetName, keyValue, expandRelationName);

	    InputStream content = execute(absolutUri, contentType, HTTP_METHOD_GET);

	    return EntityProvider.readEntry(contentType,
	        entityContainer.getEntitySet(entitySetName),
	        content,
	        EntityProviderReadProperties.init().build());
	  }

	  private InputStream logRawContent(String prefix, InputStream content, String postfix) throws IOException {
	    if(PRINT_RAW_CONTENT) {
	      byte[] buffer = streamToArray(content);
	      print(prefix + new String(buffer) + postfix);
	      return new ByteArrayInputStream(buffer);
	    }
	    return content;
	  }

	  private byte[] streamToArray(InputStream stream) throws IOException {
	    byte[] result = new byte[0];
	    byte[] tmp = new byte[8192];
	    int readCount = stream.read(tmp);
	    while(readCount >= 0) {
	      byte[] innerTmp = new byte[result.length + readCount];
	      System.arraycopy(result, 0, innerTmp, 0, result.length);
	      System.arraycopy(tmp, 0, innerTmp, result.length, readCount);
	      result = innerTmp;
	      readCount = stream.read(tmp);
	    }
	    stream.close();
	    return result;
	  }

	  private HttpStatusCodes checkStatus(HttpURLConnection connection) throws IOException {
	    HttpStatusCodes httpStatusCode = HttpStatusCodes.fromStatusCode(connection.getResponseCode());
	    if (400 <= httpStatusCode.getStatusCode() && httpStatusCode.getStatusCode() <= 599) {
	      throw new RuntimeException("Http Connection failed with status " + httpStatusCode.getStatusCode() + " " + httpStatusCode.toString());
	    }
	    return httpStatusCode;
	  }

	  private String createUri(String serviceUri, String entitySetName, String id) {
	    return createUri(serviceUri, entitySetName, id, null);
	  }

	  private String createUri(String serviceUri, String entitySetName, String id, String expand) {
	    final StringBuilder absolutUri = new StringBuilder(serviceUri).append(SEPARATOR).append(entitySetName);
	    if(id != null) {
	      absolutUri.append("(").append(id).append(")");
	    }
	    if(expand != null) {
	      //absolutUri.append("/?$expand=").append(expand);
	      absolutUri.append("?$(").append(expand).append(")");
	    }
	    return absolutUri.toString();
	  }

	  private InputStream execute(String relativeUri, String contentType, String httpMethod) throws IOException {
	    HttpURLConnection connection = initializeConnection(relativeUri, contentType, httpMethod);

	    connection.connect();
	    checkStatus(connection);

	    InputStream content = connection.getInputStream();
	    content = logRawContent(httpMethod + " request on uri '" + relativeUri + "' with content:\n  ", content, "\n");
	    return content;
	  }

	  private HttpURLConnection initializeConnection(String absolutUri, String contentType, String httpMethod)
	      throws MalformedURLException, IOException {
	    URL url = new URL(absolutUri);
	    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
	    
	    connection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString(String.format("%s:%s", "ユーザ名", "パスワード").getBytes()));

	    connection.setRequestMethod(httpMethod);
	    connection.setRequestProperty(HTTP_HEADER_ACCEPT, contentType);
	    if(HTTP_METHOD_POST.equals(httpMethod) || HTTP_METHOD_PUT.equals(httpMethod)) {
	      connection.setDoOutput(true);
	      connection.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, contentType);
	    }

	    return connection;
	  }
}


結果は以下

----- Generate sample data ------------------------------

----- Read Edm ------------------------------
GET request on uri 'http://IPアドレス:8090/toku/oData/sensorjump.xsodata/$metadata' with content:
  <?xml version="1.0" encoding="utf-8" standalone="yes" ?><edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"><edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0"><Schema Namespace="toku.oData.sensorjump" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2008/09/edm"><EntityType Name="TOKUTESTType"><Key><PropertyRef Name="ID" /></Key><Property Name="ID" Type="Edm.String" Nullable="false" MaxLength="5" /><Property Name="NAME" Type="Edm.String" MaxLength="50" /><Property Name="GROUP" Type="Edm.String" MaxLength="50" /><Property Name="CREATE_DATE" Type="Edm.DateTime" /></EntityType><EntityContainer Name="sensorjump" m:IsDefaultEntityContainer="true"><EntitySet Name="TOKUTEST" EntityType="toku.oData.sensorjump.TOKUTESTType" /></EntityContainer></Schema></edmx:DataServices></edmx:Edmx>

Read default EntityContainer: sensorjump

----- Read Feed ------------------------------
GET request on uri 'http://IPアドレス:8090/toku/oData/sensorjump.xsodata/TOKUTEST' with content:
  {"d":{"results":[{"__metadata": {"type":"toku.oData.sensorjump.TOKUTESTType","uri":"http://IPアドレス:8090/toku/oData/sensorjump.xsodata/TOKUTEST('GV001')"},"ID":"GV001","NAME":"NG TOKU","GROUP":"SYSTEM CONSULTING GROUP","CREATE_DATE":"\/Date(1586736000000)\/"},{"__metadata": {"type":"toku.oData.sensorjump.TOKUTESTType","uri":"http://IPアドレス:8090/toku/oData/sensorjump.xsodata/TOKUTEST('GV002')"},"ID":"GV002","NAME":"NG TOKU2","GROUP":"SYSTEM CONSULTING GROUP","CREATE_DATE":"\/Date(1586736000000)\/"},{"__metadata": {"type":"toku.oData.sensorjump.TOKUTESTType","uri":"http://IPアドレス:8090/toku/oData/sensorjump.xsodata/TOKUTEST('GV003')"},"ID":"GV003","NAME":"NG TOKU3","GROUP":"SYSTEM CONSULTING GROUP","CREATE_DATE":"\/Date(1586736000000)\/"},{"__metadata": {"type":"toku.oData.sensorjump.TOKUTESTType","uri":"http://IPアドレス:8090/toku/oData/sensorjump.xsodata/TOKUTEST('GV004')"},"ID":"GV004","NAME":"NG TOKU4","GROUP":"SYSTEM CONSULTING GROUP","CREATE_DATE":"\/Date(1586736000000)\/"}]}}

Read: 4 entries: 
##########
Entry:
GROUP: SYSTEM CONSULTING GROUP
CREATE_DATE: 20/04/13 9:00
ID: GV001
NAME: NG TOKU
##########
##########
Entry:
GROUP: SYSTEM CONSULTING GROUP
CREATE_DATE: 20/04/13 9:00
ID: GV002
NAME: NG TOKU2
##########
##########
Entry:
GROUP: SYSTEM CONSULTING GROUP
CREATE_DATE: 20/04/13 9:00
ID: GV003
NAME: NG TOKU3
##########
##########
Entry:
GROUP: SYSTEM CONSULTING GROUP
CREATE_DATE: 20/04/13 9:00
ID: GV004
NAME: NG TOKU4
##########

----- Read Entry ------------------------------
GET request on uri 'http://IPアドレス:8090/toku/oData/sensorjump.xsodata/TOKUTEST('GV002')' with content:
  {"d":{"__metadata": {"type":"toku.oData.sensorjump.TOKUTESTType","uri":"http://IPアドレス:8090/toku/oData/sensorjump.xsodata/TOKUTEST('GV002')"},"ID":"GV002","NAME":"NG TOKU2","GROUP":"SYSTEM CONSULTING GROUP","CREATE_DATE":"\/Date(1586736000000)\/"}}

Single Entry:
GROUP: SYSTEM CONSULTING GROUP
CREATE_DATE: 20/04/13 9:00
ID: GV002
NAME: NG TOKU2

うぇーい。

SCP(SAP Cloud Platform)お試し

例によってSCPの調べ物備忘録。参考サイトは以下。
SAP HANA – SAP Cloud Platform Trial版 ~環境構築編~ | Blog | グランバレイ株式会社

個人でアカウントは持ってるので、以下からログインして "Launch SAP Web IDE" をクリックして Web IDEに行き、右下にあるHelpful Linksの "SAP Cloud Platform Cockpit"から移動。
https://account.hanatrial.ondemand.com/

他は特に詰まるところもなく、問題なく作成完了。
引き続き以下をお手本に作業。
SAP HANA – SAP Cloud Platform Trial版 ~接続・開発編~ | Blog | グランバレイ株式会社

テーブル作成のSQLは以下

CREATE COLUMN TABLE "SYSTEM"."TEST"(
    "ID" CHAR(5),
    "NAME" NVARCHAR(50),
    "GROUP" NVARCHAR(50),
    "CREATE_DATE" DATE,
    PRIMARY KEY("ID")
);

データ投入のSQLは以下

INSERT INTO "SYSTEM"."TEST" VALUES(
  'GV001', 'NG TOKU', 'SYSTEM CONSULTING GROUP', CURRENT_DATE  
);

このエントリと直前のエントリ読めば、自分でテーブル作ってそこにアクセスするoDataサービス作れると思われ。

SAP HANAにoDataでアクセス お試し版

調べ物のついでに、SAP HANA express editionをVMWareで起動して、oDataでちょろっとアクセスしてみたので備忘録的に。
自分の備忘録なので、いつにもまして適当だけれども気にしない感じで。
ちなみにHANAであって、S/4 HANAじゃないぞ念のため。

1.SAP HANA express edition環境の構築

まぁ基本的にこちらの2記事に沿って導入しただけですな。
SAP HANAに触れてみよう! SAP HANA, express editionクイックインストールガイド | SAPジャパン ブログ
SAP HANAに触れてみよう!その2:SAP HANA 2.0, express edition + 各種ツール類のセットアップのご紹介 | SAPジャパン ブログ

あえて気をつけることと言えば、その2記事に記載してある通り、インスタンス番号が00ではなく90になっている事と、自分はeclipseバージョン2019-09を使っているので、HANA Studioは https://tools.hana.ondemand.com/2019-09/ からインストールしたことくらいかな。

ちなみに「Server only」 をダウンロードしたので、Web IDEやCockpitなんかの確認はしていない。
アドミンツールは http :// IPアドレス:8090/sap/hana/xs/admin で、ユーザ名はSYSTEM、パスワードは起動時に変更したものを使用する。

2.oDataアクセス環境の構築

こちらはVMWareの外からHANA studion on eclipseにて作業。
これはこちらのサイトの「5.1. HANA System の登録」と「5.2. リポジトリの登録」をお手本にして、
5. HANA XS Project の準備 — OData for SAP HANA セットアップガイド   初版 2016-12-01   intra-mart Accel Platform

お手軽にanonymousで認証すっ飛ばしたので、.xaccess以下はこちらを参考に
How to Expose a HANA Table via oData | SAP Blogs

なのでeclipse(HANA studio)のリポジトリはこんな感じ。
f:id:ngtoku:20200315185117p:plain


各々の内容は以下の通り。
.xaccess

{
	"exposed" : true, 
	"authentication" : null,
	"mime_mapping" : [{
		"extension" : "jpg",
		"mimetype" : "image/jpeg"
	}],
	"prevent_xsrf" : false,
	"force_ssl": false,
	"enable_etags" : true,
	"anonymous_connection" : "toku.oData::anonuser",
	"cors": [{
		"enabled": true,
		"allowMethods": ["GET","POST","PUT","DELETE", "HEAD"],
		"allowOrigin": ["*"]
	}],
	"allowHeaders": [
		"Accept",
		"Authorization",
		"Content-Type",
		"X-CSRF-Token",
		"Access-Control-Allow-Origin"
	],
	"exposeHeaders": [
		"x-csrf-token"
	],
	"cache_control": "no-cache, no-store"
}

ちなみに "anonymous_connection" : "toku.oData::anonuser", を "authentication" :{"method": "Basic"}, に帰るとベーシック認証になるよ。


.xsapp
空っぽ


anonuser.xssqlcc

{"description":"Anonymous SQL connection"}


参照先は以下にしたので、
f:id:ngtoku:20200315185546p:plain

sensorjump.xsodataの内容はこんな感じ。エイリアスからはアンダースコア外したほうが良かったかな、と思ったりしたがとりあえずそのまま。

service { "SYS"."ADAPTERS_" as "ADAPTERS_"; }

各々のファイルは右クリックメニューでアクティベートしておかないとアドミンツールに出てこないので注意。
あと、参考サイトそのままコピーでエラーになる場合はダブルクォートがおかしかったりするので、アクティベートでエラーになる場合は打ち直してみたりすると良い。



アドミンツールは前述の http :// IPアドレス:8090/sap/hana/xs/admin で、設定。
最初は参照先サイトのようにエラーメッセージが出るけれど、右下のeditでHANA接続IDとパスワードをセットして以下の通り。
f:id:ngtoku:20200315190020p:plain



3.ブラウザから確認

APIの情報は以下で確認
http :// IPアドレス:8090/toku/oData/sensorjump.xsodata

<service xml:base="http://IPアドレス:8090/toku/oData/sensorjump.xsodata/">
<workspace>
<atom:title>Default</atom:title>
<collection href="ADAPTERS_">
<atom:title>ADAPTERS_</atom:title>
</collection>
</workspace>
</service>


http :// IPアドレス:8090/toku/oData/sensorjump.xsodata/$metadata

<edmx:Edmx Version="1.0">
<edmx:DataServices m:DataServiceVersion="2.0">
<Schema Namespace="toku.oData.sensorjump">
<EntityType Name="ADAPTERS_Type">
<Key>
<PropertyRef Name="ADAPTER_OID"/>
</Key>
<Property Name="ADAPTER_OID" Type="Edm.Int64" Nullable="false"/>
<Property Name="OWNER_OID" Type="Edm.Int64" Nullable="false"/>
<Property Name="ADAPTER_NAME" Type="Edm.String" MaxLength="64"/>
<Property Name="PROPERTIES" Type="Edm.String" MaxLength="1000"/>
<Property Name="CONFIGURATION" Type="Edm.String" MaxLength="2147483647"/>
<Property Name="IS_SYSTEM_ADAPTER" Type="Edm.Byte" Nullable="false"/>
</EntityType>
<EntityContainer Name="sensorjump" m:IsDefaultEntityContainer="true">
<EntitySet Name="ADAPTERS_" EntityType="toku.oData.sensorjump.ADAPTERS_Type"/>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>


データ内容は以下で確認。$format=json って入れないと、レスポンスはXMLで返ってくるよ。


・データ全部
 http :// IPアドレス:8090/toku/oData/sensorjump.xsodata/ADAPTERS_?$format=json


・主キー指定(このテーブルの主キーはADAPTER_OIDなので、ADAPTER_OID=145525という条件で取得していることになる)
 最後にLがついているのは型指定かな、あんま調べてないけど。
 http :// IPアドレス:8090/toku/oData/sensorjump.xsodata/ADAPTERS_(145525L)?$format=json


・主キー&取得項目指定(ADAPTER_OIDとADAPTER_NAME)
 http :// IPアドレス:8090/toku/oData/sensorjump.xsodata/ADAPTERS_(145525L)?$format=json&$select=ADAPTER_OID,ADAPTER_NAME


・条件指定(ADAPTER_NAME='aseodbc')&取得項目指定(ADAPTER_OIDとADAPTER_NAME)
 http :// IPアドレス:8090/toku/oData/sensorjump.xsodata/ADAPTERS_?$format=json&$select=ADAPTER_OID,ADAPTER_NAME&$filter=ADAPTER_NAME%20eq%20%27aseodbc%27


ちなみに主キー指定の例だと、こんな感じのレスポンスが返ってくるよ。

{
    "d": {
        "__metadata": {
            "type": "toku.oData.sensorjump.ADAPTERS_Type",
            "uri": "http://IPアドレス:8090/toku/oData/sensorjump.xsodata/ADAPTERS_(145525L)"
        },
        "ADAPTER_OID": "145525",
        "OWNER_OID": "131072",
        "ADAPTER_NAME": "aseodbc",
        "PROPERTIES": "display_name=ASE (ODBC);description=Sybase ASE",
        "CONFIGURATION": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><RemoteSourceDescription name=\"Root\" id=\"1\">略</RemoteSourceDescription>",
        "IS_SYSTEM_ADAPTER": 1
    }
}

備忘メモ(Terraform)

NLB作ってて、「Network Load Balancers do not support Stickiness」というエラーが出たのでググったら以下がヒット。
aws_lb_target_group: When type is network, stickiness not supported · Issue #2746 · terraform-providers/terraform-provider-aws · GitHub

どうもsticknessのデフォルトがenabledの様なのでenabled = falseを明示したsticknessを追加したら動いた。

resource "aws_lb_target_group" "ecs_nlb_toku" {
	name = "ecs-nlb-toku"
	target_type = "ip"
	port	= 8080
	protocol = "TCP"
	vpc_id = aws_vpc.toku.id
	deregistration_delay = 300
 	health_check {
		interval = 30
		port = "traffic-port"
		protocol = "HTTP"
		healthy_threshold = 3
		unhealthy_threshold = 3
	}
	depends_on = [aws_lb.ecs_nlb_toku]
	stickiness {
		type = "lb_cookie"
		enabled = false
	}
}

公式ドキュメントは以下、デフォルトがtrueであることが確認可能。
AWS: aws_lb_target_group - Terraform by HashiCorp

ちなみに5行目は最初HTTPって記載していたんだけど、以下エラーが出たのでTCPにしてある。
Error: Error creating LB Listener: IncompatibleProtocols: The listener and its associated target group 'arn:aws:elasticloadbalancing:us-east-1:XXXXXXXXXXXX:targetgroup/ecs-nlb-toku/XXXXXXXXXXXXXXXX' have incompatible protocols

多分これだと思う。
NLB incompatible protocols · Issue #4955 · terraform-providers/terraform-provider-aws · GitHub