SAP CP Trial環境でMulti Target Applicationを作ってみる【ODataService_V2の作成】

# はじめに

以前の記事でSAP CloudPlatform Trial環境でのMTAプロジェクト(Multi Target Application)を作成し、
DBModuleやJavaModuleの追加をしました。

【DBModuleの追加】
　[SAP CP Trial環境でMulti Target Applicationを作ってみる【DBModuleの作り方】](https://qiita.com/yonekura-h/items/6aea4da68da8e99fdace)

【JavaModuleの追加】
[SAP CP Trial環境でMulti Target Applicationを作ってみる【JavaアプリとDBModuleの連携】](https://qiita.com/yonekura-h/items/0e1e16b303d4a4ed6503)


今回はJavaを使ってODataServiceををMTAプロジェクト内に作成してみます。



# JavaModuleの追加（OData V2 Service)
ODataServiceを作るにあたって、
もともとの想定ではSpling Boot Applicationと[Apache Olingo](http://olingo.apache.org/)を使って
ODataServiceを作成しようと考えていました。
そのためJavaModuleの追加時にはSpling Boot Applicationを追加しましたが、
JavaModuleのテンプレートのなかにODataServiceが作れそうな選択肢が用意されているので
こちらから作成してみます。

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/0f3194cb-3b48-6755-36e9-dad798e9fc4a.png)


[ドキュメント](https://help.sap.com/viewer/825270ffffe74d9f988a0f0066ad59f0/CF/en-US/ca3733f0bb5748c39568952e51f86d16.html)を参照するとSAP Cloud Platform SDK for service developmentがベースになっているとのことなので、
以下の一連のブログを参考としています。

【参考URL】
[SAP Cloud Platform SDK for service development: Overview of Blogs](https://blogs.sap.com/2018/03/01/sap-cloud-platform-sdk-for-service-development-overview-of-blogs/)

上記ではODataService V4を作成していますが、
今回はブログを参考にしつつ、ODataService V2を作成します。





# Javaクラス、定義情報（Metadata）の追加
参考URLをもとに`ServiceImplementation.java`と`DemoService.xml`を追加します。

**※ServiceImplementation.javaの追加**
OData V2 ServiceをdemoODSv2というモジュール名で作成した場合、下図のように`src`配下の最下層で右クリック -> New -> Java Class 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/7a861b36-c03d-8790-779e-bab6f1e4ffb1.png)

起動する子画面にServiceImplementation`を入力してNext -> finish押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/5729990b-1c8b-ca10-7154-f05c6900a163.png)

ファイルの内容は以下の通り。


```java:ServiceImplementation.java
package <作成したJavaClassのものを使用>;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.cloud.sdk.service.prov.api.operations.Query;
import com.sap.cloud.sdk.service.prov.api.operations.Read;
import com.sap.cloud.sdk.service.prov.api.request.QueryRequest;
import com.sap.cloud.sdk.service.prov.api.request.ReadRequest;
import com.sap.cloud.sdk.service.prov.api.response.ErrorResponse;
import com.sap.cloud.sdk.service.prov.api.response.QueryResponse;
import com.sap.cloud.sdk.service.prov.api.response.ReadResponse;


public class ServiceImplementation {	


	@Query(serviceName="DemoService", entity="People")
	public QueryResponse getPeople(QueryRequest request) {
		List<Map<String, Object>> peopleMap = getPeople();
		return QueryResponse.setSuccess().setDataAsMap(peopleMap).response();
	}

	
	@Read(serviceName="DemoService", entity="People")
	public ReadResponse getPerson(ReadRequest request) {
		
		// retrieve the requested person from URI 
		Map<String, Object> keyPredicates = request.getKeys();
		Object keyObject = keyPredicates.get("UniqueId");
		Integer id = (Integer)keyObject;
		
		// search the requested person in the database
		List<Map<String, Object>> peopleList = getPeople();
		Map<String, Object> requestedPersonMap = new HashMap<String, Object>();
		for(Map<String, Object> personMap : peopleList) {
			if(((Integer)personMap.get("UniqueId")).equals(id)) {
				// found it
				requestedPersonMap = personMap;
			}
		}		
		
		// handle error: "not found" 
		if(requestedPersonMap.isEmpty()) {
			Logger logger = LoggerFactory.getLogger(ServiceImplementation.class);
			logger.error("Person with UniqueId " + id + " doesn't exist! Service request was invoked with invalid key value"); 
			ErrorResponse response = ErrorResponse.getBuilder()
					.setMessage("Person with UniqueId " + id + " doesn't exist!")
					.setStatusCode(404)
					.response();
			return ReadResponse.setError(response);
		}
		
		return ReadResponse.setSuccess().setData(requestedPersonMap).response();
	}

	
	/* Dummy Database */
	
	private List<Map<String, Object>> getPeople(){
		List<Map<String, Object>> peopleMap = new ArrayList<Map<String, Object>>();
		
		peopleMap.add(createPerson(0, "Anna"));
		peopleMap.add(createPerson(1, "Berta"));
		peopleMap.add(createPerson(2, "Claudia"));
		peopleMap.add(createPerson(3, "Debbie"));
		
		return peopleMap;
	}
	
	private Map<String, Object> createPerson(Integer id, String name){
		Map<String, Object> personMap = new HashMap<String, Object>();
		
		personMap.put("UniqueId", id);
		personMap.put("Name", name);
		
		return personMap;
	}
	
}
```


**※DemoService.xmlの追加**
下図のように`resource/edmx`で右クリック -> New -> File押下
File名に`DemoService.xml`を入力してOK押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/2c24427f-4433-7106-fb17-4be5c457d0e9.png)

ファイルの内容は以下の通り。

```xml:DemoService.xml
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
	<edmx:DataServices>
		<Schema Namespace="demo" xmlns="http://docs.oasis-open.org/odata/ns/edm">
			<EntityType Name="Person">
				<Key>
					<PropertyRef Name="UniqueId" />
				</Key>
				<Property Name="UniqueId" Type="Edm.Int32" />
				<Property Name="Name" Type="Edm.String" />
			</EntityType>
			<EntityContainer Name="container" >
				<EntitySet Name="People" EntityType="demo.Person" />
			</EntityContainer>
		</Schema>
	</edmx:DataServices>
</edmx:Edmx>
```


この段階のJavaではDBに接続せず、固定値のデータを作成しています

```java
		peopleMap.add(createPerson(0, "Anna"));
		peopleMap.add(createPerson(1, "Berta"));
		peopleMap.add(createPerson(2, "Claudia"));
		peopleMap.add(createPerson(3, "Debbie"));
```

このままJavaアプリケーションをRunしてみました。
以下のURLにアクセスするとなんとなく動いている感じがする画面が表示されます。
`https://<javaアプリのURL参照>.cfapps.eu10.hana.ondemand.com/odata/v2/`


![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/ec6bef40-ff0f-4896-9c6b-fdd8a9ffb40a.png)


ただ、表示されたODataServiceのURLにアクセスしても期待したODataのレスポンスは得られませんでした。

ちなみに、平行して確認していたOData V4では同様のの内容で動作することが確認できました。
![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/ae644672-e90f-e418-ecf9-67c831cdcd84.png)
![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/b56eee37-ca10-3e84-2898-7b03e27c79d5.png)

調べると`DemoService.xml`がOData v4用の定義だったことが原因らしいので、
OData v2用に`DemoService.xml`の中身を編集します。

```xml:DemoService.xml(v2)

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <edmx:DataServices m:DataServiceVersion="2.0">
		<Schema Namespace="demo" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
			<EntityType Name="Person">
				<Key>
					<PropertyRef Name="UniqueId" />
				</Key>
				<Property Name="UniqueId" Type="Edm.Int32" />
				<Property Name="Name" Type="Edm.String" />
			</EntityType>
			<EntityContainer Name="container" m:IsDefaultEntityContainer="true">
				<EntitySet Name="People" EntityType="demo.Person" />
			</EntityContainer>
		</Schema>
    </edmx:DataServices>
</edmx:Edmx>

```

上記に差し替えをおこなったあと、再度Javaアプリケーションを実行します。
![図6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/cf86cc71-02de-15c5-4974-b1b64b480acc.png)

OData V2が動作していることが確認できました。
URLの末尾に`/People`を追加して仮データが参照できることも確認できます。
![図7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/4fac5dc3-96c7-c9a5-ee42-ef5253bd5346.png)


上記まででJavaでのOData v2が動作することが確認できたので、
DBから値を取得できるようにJavaのソースを改修します。

以前作成したSpling Boot Applicationのソースコードを参考にして、
DBとの接続を試します。

以下のコードではリクエストしたらDBに接続し、
成功した場合はJavaのログに以下を表示するようにしてみます。
 ` !!!! GET CURRENT !!!! <スキーマ名> ` 

```java:ServiceImplementation.java
package com.company.javatest.ODSv2;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.cloud.sdk.service.prov.api.operations.Query;
import com.sap.cloud.sdk.service.prov.api.operations.Read;
import com.sap.cloud.sdk.service.prov.api.request.QueryRequest;
import com.sap.cloud.sdk.service.prov.api.request.ReadRequest;
import com.sap.cloud.sdk.service.prov.api.response.ErrorResponse;
import com.sap.cloud.sdk.service.prov.api.response.QueryResponse;
import com.sap.cloud.sdk.service.prov.api.response.ReadResponse;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.json.JSONArray;
import org.json.JSONObject;
import java.sql.DriverManager;

public class ServiceImplementation {	


	@Query(serviceName="DemoService", entity="People")
	public QueryResponse getPeople(QueryRequest request) {
		List<Map<String, Object>> peopleMap = getPeople();
		return QueryResponse.setSuccess().setDataAsMap(peopleMap).response();
	}

	
	@Read(serviceName="DemoService", entity="People")
	public ReadResponse getPerson(ReadRequest request) {
		
		// retrieve the requested person from URI 
		Map<String, Object> keyPredicates = request.getKeys();
		Object keyObject = keyPredicates.get("UniqueId");
		Integer id = (Integer)keyObject;
		
		// search the requested person in the database
		List<Map<String, Object>> peopleList = getPeople();
		Map<String, Object> requestedPersonMap = new HashMap<String, Object>();
		for(Map<String, Object> personMap : peopleList) {
			if(((Integer)personMap.get("UniqueId")).equals(id)) {
				// found it
				requestedPersonMap = personMap;
			}
		}		
		
		// handle error: "not found" 
		if(requestedPersonMap.isEmpty()) {
			Logger logger = LoggerFactory.getLogger(ServiceImplementation.class);
			logger.error("Person with UniqueId " + id + " doesn't exist! Service request was invoked with invalid key value"); 
			ErrorResponse response = ErrorResponse.getBuilder()
					.setMessage("Person with UniqueId " + id + " doesn't exist!")
					.setStatusCode(404)
					.response();
			return ReadResponse.setError(response);
		}
		
		return ReadResponse.setSuccess().setData(requestedPersonMap).response();
	}
	
	/* Connection DataBase */
	private List<Map<String, Object>> getPeople(){
		List<Map<String, Object>> peopleMap = new ArrayList<Map<String, Object>>();
		try {
			Connection conn = getConnection();
			if (conn != null) {
				String currentUser = "";
				PreparedStatement prepareStatement = conn.prepareStatement("SELECT CURRENT_USER \"current_user\" FROM DUMMY;");
				ResultSet resultSet = prepareStatement.executeQuery();
				int column = resultSet.findColumn("current_user");
				while (resultSet.next()) {
					// peopleMap.add(createPerson(resultSet.findColumn("ID"), resultSet.findColumn("NAME")));
				  currentUser += resultSet.getString(column);
				}
				System.out.println("!!!! GET CURRENT !!!!" + currentUser);

				peopleMap = getDummyPeople();
			} else {
				// builder.append("no");
				peopleMap = getDummyPeople();
			}
		} catch (SQLException e) {
			// builder.append("no");
			System.out.println("SQL Exception");
		}
		
		return peopleMap;
	}
	private Connection getConnection() {
		Connection conn = null;
		String DB_USERNAME = "";
		String DB_PASSWORD = "";
		String DB_HOST = "";
		String DB_PORT = "";
		try {
			JSONObject obj = new JSONObject(System.getenv("VCAP_SERVICES"));
			JSONArray arr = obj.getJSONArray("hanatrial");
			DB_USERNAME = arr.getJSONObject(0).getJSONObject("credentials").getString("user");
			DB_PASSWORD = arr.getJSONObject(0).getJSONObject("credentials").getString("password");
			DB_HOST = arr.getJSONObject(0).getJSONObject("credentials").getString("host").split(",")[0];
			DB_PORT = arr.getJSONObject(0).getJSONObject("credentials").getString("port");
			 // String DB_READ_CONNECTION_URL = "jdbc:sap://" + DB_HOST + ":" + DB_PORT;
			String DB_READ_CONNECTION_URL = arr.getJSONObject(0).getJSONObject("credentials").getString("url");
			conn = (Connection) DriverManager.getConnection(DB_READ_CONNECTION_URL, DB_USERNAME, DB_PASSWORD);
		} catch (Exception e) {
        	System.out.println("Connection Error");
		}
		return conn;
	}
	/* Dummy Database */
	private List<Map<String, Object>> getDummyPeople(){
		List<Map<String, Object>> peopleMap = new ArrayList<Map<String, Object>>();
		
		peopleMap.add(createPerson(0, "Anna"));
		peopleMap.add(createPerson(1, "Berta"));
		peopleMap.add(createPerson(2, "Claudia"));
		peopleMap.add(createPerson(3, "Debbie"));
		
		return peopleMap;
	}
	
	private Map<String, Object> createPerson(Integer id, String name){
		Map<String, Object> personMap = new HashMap<String, Object>();
		
		personMap.put("UniqueId", id);
		personMap.put("Name", name);
		
		return personMap;
	}
	
}
```

上記のソースコードに置き換えてJava Applicationを再実行し、
もう一度`/odata/v2/DemoService/People`をリクエストします。

以下のようにログが出力されたのでDBとの接続はできているみたいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/cd9c0941-f26d-0bbf-eef6-b454df3f6bc2.png)

# DBインスタンスにデータを作成する
WebIDEから実行したJava Applicationが参照しているDBインスタンスにテストデータを作成します。

実行中のURLを参考に、コクピット画面からSpace内に存在するApplicationの詳細を確認します。
右側メニューから`Service Bindings`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/d624b347-7e96-8144-7571-42623cc442e6.png)

JavaインスタンスにバインドされているDBインスタンスの名称がわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/c3ec3d88-1246-78a8-4dd4-e496469139c0.png)

上記でインスタンス名称がわかったら、以下赤枠を押下し、
WebIDEのDataBase Explorerを表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/c359829a-6ea3-fd5b-3150-cee1345fbaf4.png)


「Add DataBase」で対象のDBを選択してOKを押下します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/0b5e2331-f6e3-bfb0-5eca-c015fbcac977.png)

DBとの接続ができるとTable等確認ができますが、
まだ何も作成されていないため、Tablesを選択しても何も出てきません。
なので左上にあるSQLコンソールからCreate文を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/3b3de618-5d61-5925-54ed-afd30e547c91.png)


```SQL
create table people (UniqueId SMALLINT CS_INT not null primary key, Name varchar(50));
```

上記のSQLを実行し、`People`テーブルを作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/cf93bb2d-a08f-aa94-a038-7a950140f7e9.png)

右上あたりの`Open Data`を押下するとGUIでデータを作れるので、
以下のようなテストデータを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/ded96ba2-d89c-ef55-2483-cb9209779b2e.png)


これでおそらくJavaからデータが取得できると思われます。

# JavaからDBのデータを取得する

先ほど作成したJavaファイルの中身を変更し、Peopleテーブルに作ったデータを呼び出してみます。
 ダミーデータを作成していた関数：getPeopleの内容を以下のように変更します。

```java:ServiceImplementation.java
	/* Connection DataBase */
	private List<Map<String, Object>> getPeople(){
		List<Map<String, Object>> peopleMap = new ArrayList<Map<String, Object>>();
		try {
			Connection conn = getConnection();
			if (conn != null) {
				String currentUser = "";
				PreparedStatement prepareStatement = conn.prepareStatement("SELECT * FROM PEOPLE;");
				ResultSet resultSet = prepareStatement.executeQuery();
				int column_id = resultSet.findColumn("UNIQUEID");
				int column_name = resultSet.findColumn("NAME");
				while (resultSet.next()) {
					peopleMap.add(createPerson(Integer.valueOf(resultSet.getString(column_id)), resultSet.getString(column_name)));
				  //currentUser += resultSet.getString(column);
				}
				// System.out.println("!!!! GET CURRENT !!!!" + currentUser);

				// peopleMap = getDummyPeople();
			} else {
				// builder.append("no");
				peopleMap = getDummyPeople();
			}
		} catch (SQLException e) {
			// builder.append("no");
			System.out.println("SQL Exception");
		}
		
		return peopleMap;
	}

```

Java Applicationを再実行し、
もう一度`/odata/v2/DemoService/People`をリクエストします。
以下のように、Peopleテーブルに作成したデータが取得できるようになりました！


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/adfb677c-9c12-f6ce-c55c-0b706c49f777.png)

`/odata/v2/DemoService/People(1)`など、
UniqueIDを指定して絞り込みもできています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/d2c53ab0-654c-3f13-88fb-34b829c807b3.png)

ただ、上記サンプルのソースコードではハードコーディングのテストデータを絞り込む処理をそのまま流用しているので、
SQLでJava側に全レコードを取得した後に一致するIDのレコードのみに絞り込む動作をしています。
改善が必要です。

また、$filterなどのクエリオプションに関しても実装できていないため、
ここからJavaの作りこみが必要になると思います。

# おわりに

Java Applicationの実装に関してはODataとして不足している機能が多いですが、
ひとまず当記事の内容で
　DB ⇔ OData(Java)
までは実装できたかと思います。

次はODataをFioriアプリケーション（UIモジュール）から呼び出せるようにしたいと思います。