2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HCPとcloudBitでIoT - 4.受け取ったデータをDBに格納

Last updated at Posted at 2015-07-01

前回CloudbitSubscriptionServletクラスに、

  • doPostメソッド - littleBits社のクラウドから呼び出され、受け取ったデータをDBに格納する(ので人が直接結果を見ることはない)
  • doGetメソッド - WebブラウザでURL指定時に呼び出され、DB中のデータを表示する(ので人が目視で確認できる)

の枠を用意しておきました。

さて、cloudBitから受け取ったデータはCloudbitEventオブジェクトに格納できるように準備しましたが、DBへのデータの格納、およびDBからのデータの取り出しのために、CloudbitEventDAOクラスを定義します。

public class CloudbitEventDAO {
	protected DataSource dataSource;

	public CloudbitEventDAO(DataSource newDataSource) throws SQLException {
		setDataSource(newDataSource);
	}

	public DataSource getDataSource() {
		return dataSource;
	}
}

コンストラクタでアクセス先のDBのDataSourceオブジェクトを格納しておくようにします。setDataSource()メソッドの中を見てみましょう。

	public void setDataSource(DataSource newDataSource) throws SQLException {
		this.dataSource = newDataSource;
		Connection connection = dataSource.getConnection();
		try {
			PreparedStatement pstmt = connection.prepareCall(
					"set schema NEO_XXXXXXXXXXXXXXXXXXXXXXXXX");
			pstmt.execute();
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
		checkTable();
	}

ここでは、インスタンス変数dataSourceに引数で渡されたオブジェクトを格納しておくだけでなく、前回も触れさせていただいたSAP HANA Cloud Platform(HCP)を使う際に、これから使用しようとしているテーブルがどのスキーマに属しているのかをset schema文で設定しています。その後に、checkTable()メソッドでCloudbitEventオブジェクトの格納先となるテーブルの有無を確認します。

	void checkTable() throws SQLException {
		Connection connection = null;
		try {
			connection = dataSource.getConnection();
			if (!existsTable(connection)) {
				createTable(connection);	// wouldn't be called
			}
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
	}

	protected boolean existsTable(Connection conn) throws SQLException {
		String tableName = "EVENTS";
		DatabaseMetaData meta = conn.getMetaData();
		ResultSet rs = meta.getTables(null, null, tableName, null);
		while (rs.next()) {
			String name = rs.getString("TABLE_NAME");
			if (name.equals(tableName)) {
				return true;
			}
		}
		return false;
	}

	void createTable(Connection conn) throws SQLException {
		PreparedStatement pstmt = conn.prepareStatement(
				"CREATE COLUMN TABLE EVENTS (" +
				"EVENT_ID   varchar(255)," +
				"EVENT_TYPE varchar(255)," +
				"EVENT_TIMESTAMP bigint," +
				"EVENT_USERID integer," +
				"EVENT_BITID  varchar(255)," +
				"PAYLOAD_ABSOLUTE integer," +
				"PAYLOAD_PERCENT integer," +
				"PAYLOAD_LEVEL varchar(255)," +
				"PAYLOAD_DELTA varchar(255)," +
				"PRIMARY KEY (EVENT_ID))");
		pstmt.execute();
		pstmt.close();
	}

existsTable()メソッドで格納先テーブル(EVENTS)の有無を確認し、まだ存在していないようであれば、createTable()メソッドでEVENTSテーブルを新規に作成しています。テーブルの存在確認、およびテーブルの新規作成(createTable()メソッド内のCREATE TABLE文)に関しては、HCP以外の環境では使用するDBに合わせて修正が必要となるでしょう。CREATE TABLE文にCOLUMNが入っているのは、インメモリ+カラム型テーブルを使用するためです(CREATE TABLEマニュアル)。

さて、データをDBに格納するために、addRow()メソッドを用意します。引数にはデータを格納したCloudbitEventオブジェクトを指定します。

	public void addRow(CloudbitEvent data) throws SQLException {
		CloudbitEvent eventData = (CloudbitEvent) data;
		Connection connection = dataSource.getConnection();
		try {
			PreparedStatement pstmt = connection.prepareCall(
					"INSERT INTO EVENTS " + 
					"(EVENT_ID,EVENT_TYPE,EVENT_TIMESTAMP,EVENT_USERID,EVENT_BITID," +
					"PAYLOAD_ABSOLUTE,PAYLOAD_PERCENT,PAYLOAD_LEVEL,PAYLOAD_DELTA) " +
					"VALUES(?,?,?,?,?,?,?,?,?)");
			pstmt.setString(1, UUID.randomUUID().toString());
			pstmt.setString(2, eventData.getEventType());
			pstmt.setLong(3, eventData.getEventTimestamp());
			pstmt.setInt(4, eventData.getEventUserId());
			pstmt.setString(5, eventData.getEventBitId());
			pstmt.setInt(6,  eventData.getPayloadAbsolute());
			pstmt.setInt(7, eventData.getPayloadPercent());
			pstmt.setString(8,  eventData.getPayloadLevel());
			pstmt.setString(9, eventData.getPayloadDelta());
			pstmt.executeUpdate();
			pstmt.close();
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
	}

一方、データの取り出しには、selectAllRows()を使います(Allとは言いつつも、ここでは直近のものから最新100件までにしてあります)。

	public List<CloudbitEvent> selectAllRows() throws SQLException {
		Connection connection = dataSource.getConnection();
		try {
			PreparedStatement pstmt = connection.prepareStatement(
					"SELECT EVENT_ID,EVENT_TYPE,EVENT_TIMESTAMP,EVENT_USERID,EVENT_BITID," +
					"PAYLOAD_ABSOLUTE,PAYLOAD_PERCENT,PAYLOAD_LEVEL,PAYLOAD_DELTA " +
					"FROM EVENTS ORDER BY EVENT_TIMESTAMP DESC");
			ResultSet rs = pstmt.executeQuery();
			ArrayList<CloudbitEvent> list = new ArrayList<CloudbitEvent>();
			int count = 0;
			while (rs.next() && count++ < 100) {
				CloudbitEvent p = new CloudbitEvent();
				p.setEventId(rs.getString(1));
				p.setEventType(rs.getString(2));
				p.setEventTimestamp(rs.getInt(3));
				p.setEventUserId(rs.getInt(4));
				p.setEventBitId(rs.getString(5));
				p.setPayloadAbsolute(rs.getInt(6));
				p.setPayloadPercent(rs.getInt(7));
				p.setPayloadLevel(rs.getString(8));
				p.setPayloadDelta(rs.getString(9));
				list.add(p);
			}
			pstmt.close();
			return list;
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
	}

ここまで、どちらかというとDBアクセスまわりの力技系の実装を見てきました。ここから、前回に枠組みを作っておいたCloudbitSubscriptionServletクラスに移りましょう。まずは、先ほど実装したCloudbitEventDAOクラスを利用するための準備をします。

	private CloudbitEventDAO cloudbitEventDAO;
      
	public void init() throws ServletException {
		super.init();
		try {
			InitialContext ctx = new InitialContext();
			DataSource dataSource = (DataSource) ctx
					.lookup("java:comp/env/jdbc/DefaultDB");
			cloudbitEventDAO = new CloudbitEventDAO(dataSource);
		} catch (SQLException e) {
			throw new ServletException(e);
		} catch (NamingException e) {
			throw new ServletException(e);
		}
	}

また、web.xmlファイルの中にlookupしているデータソースに関する情報を追加しておきます。

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" 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">
  <resource-ref>
    <res-ref-name>jdbc/DefaultDB</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
  </resource-ref>
</web-app>

HCP以外の環境で使用する場合には、DataSourceオブジェクトの取得に関しては、修正が必要になりかもしれません。いずれにせよ、これ以降、メンバ変数cloudbitEventDAOを経由してDBへのアクセスが可能になりました。

次に、doPost()メソッド、およびdoGet()メソッドの中身を見て行きましょう。

まずは、littleBitsクラウドからの呼び出しを受けるdoPost()メソッドです。前回までの時点では何もしない実装のままでしたが、ここでは、実際の処理はdoAdd()メソッドで行うようにしています。

	protected void doPost(HttpServletRequest request, 
			HttpServletResponse response) throws ServletException, IOException {
		try {
			doAdd(request);
		} catch (Exception e) {
			response.getWriter().println(
					"Database operation(POST) failed with reason: " +
					e.getMessage());
			LOGGER.error("Database operation(POST) failed", e);
		}
	}

さて、実際に処理を行うdoAdd()メソッドです。

	private static final String JSON_TYPE = "type";
	private static final String JSON_TIMESTAMP = "timestamp";
	private static final String JSON_USERID = "user_id";
	private static final String JSON_BITID = "bit_id";
	private static final String JSON_PAYLOAD = "payload";
	private static final String JSON_ABSOLUTE = "absolute";
	private static final String JSON_PERCENT = "percent";
	private static final String JSON_LEVEL = "level";
	private static final String JSON_DELTA = "delta";
	
	private void doAdd(HttpServletRequest request) throws ServletException, IOException, SQLException {
		BufferedReader reader = new BufferedReader(request.getReader());
		StringBuffer body = new StringBuffer();
		String line;
		
		while ((line = reader.readLine()) != null) {
			body.append(line);
		}
		
		CloudbitEvent eventData = new CloudbitEvent();
		try {
			JSONObject json = new JSONObject(body.toString());
			if (json.has(JSON_TYPE)) {
				eventData.setEventType(json.getString(JSON_TYPE));
			}
			if (json.has(JSON_TIMESTAMP)) {
				eventData.setEventTimestamp(json.getInt(JSON_TIMESTAMP));
			}
			if (json.has(JSON_USERID)) {
				eventData.setEventUserId(json.getInt(JSON_USERID));
			}
			if (json.has(JSON_BITID)) {
				eventData.setEventBitId(json.getString(JSON_BITID));
			}
			if (json.has(JSON_PAYLOAD)) {
				JSONObject json2 = json.getJSONObject(JSON_PAYLOAD);
				if (json2.has(JSON_ABSOLUTE)) {
					eventData.setPayloadAbsolute(json2.getInt(JSON_ABSOLUTE));
				}
				if (json2.has(JSON_PERCENT)) {
					eventData.setPayloadPercent(json2.getInt(JSON_PERCENT));
				}
				if (json2.has(JSON_LEVEL)) {
					eventData.setPayloadLevel(json2.getString(JSON_LEVEL));
				}
				if (json2.has(JSON_DELTA)) {
					eventData.setPayloadDelta(json2.getString(JSON_DELTA));
				}
			}
		} catch (Exception e) {
			new IOException("JSON parse exception: " + e.getMessage());
		}
		cloudbitEventDAO.addRow(eventData);
	}

ソースコード自体は長いものの、内部的にはlittleBitsクラウドから通知されたJSON形式のデータを解析しながらCloudbitEventオブジェクトに設定していき、最後に先ほど紹介したCloudbitEventDAOクラスのaddRow()メソッドでDBにINSERTするという単純なものです。

なお、JSON形式のデータの解析には、json.orgで公開されているパーザを使用しています。今回の範囲であれば、JSONArray, JSONException, JSONObject, JSONString, JSONTokenerの5クラスがあれば用が足ります。

さて、受け取ったデータの表示の方に移りましょう。

doGet()メソッドで最後の</body></html>を出力する直前に、以下の部分を追加します。

		try {
			appendTableData(request, response);
		} catch (Exception e) {
			writer.println(
					"Database operation(GET) failed with reason: " +
							e.getMessage());
		}

実際に出力を行うのは、appendTableData()メソッドです。

	void appendTableData(HttpServletRequest request, HttpServletResponse response) 
			throws SQLException, IOException {
		PrintWriter writer = response.getWriter();
		
		List<CloudbitEvent> resultList = cloudbitEventDAO.selectAllRows();
		writer.println(
				"<table><tbody><tr><th colspan=\"6\">" +
				(resultList.isEmpty() ? "No" : resultList.size()) +
				" Record(s) in HCP</th></tr>");
		writer.println("<tr><th>Timestamp</th>" +
				"<th>Label</th><th>Absolute</th><th>Percent</th><th>Level</th><th>Delta</th></tr>");
		IXSSEncoder xssEncoder = XSSEncoder.getInstance();
		for (CloudbitEvent p : resultList) {
			Date date = new Date(p.getEventTimestamp());
			String eventBitId = p.getEventBitId();
			// Timestamp
			writer.println("<tr><td>" +
					xssEncoder.encodeHTML(date.toString()) +
					"</td>");
			// Label
			String label = "(" + eventBitId + ")";
			writer.println("<td>" + xssEncoder.encodeHTML(label) + "</td>");
			// Absolute
			writer.println("<td>" +
					p.getPayloadAbsolute() +
					"</td>");
			// Percent
			writer.println("<td>" +
					p.getPayloadPercent() +
					"</td>");
			// Level
			writer.println("<td>" + 
					xssEncoder.encodeHTML(p.getPayloadLevel()) +
					"</td>");
			// Delta
			writer.println("<td>" + 
					xssEncoder.encodeHTML(p.getPayloadDelta()) + 
					"</td></tr>");
		}
		writer.println("</tbody></table>");
	}

DBから取得したデータをテーブル形式で表示するだけの単純な内容ですが、ここでは、Stringデータを出力する際には、HCPのSDKが提供するIXSSEncoderを使って、不正なデータが原因となってXSS(クロスサイトスクリプティング)が発生しないようにフィルタリングしています。

最後に動作確認です。現時点ではcloudBitから送られてきたデータをDBに格納してはいないものの、前回と同様、ブラウザ上でdoGet()メソッドの出力画面が見られれば(別の言い方をすれば、DBアクセスに問題がなければ)動作検証できた、ということにしておきましょう。

例によって、ソースコードに関しては、github上でcloudbit-javaプロジェクトを構築しました。さらに今回使った内容に関しては、3-CloudBit_Postブランチにて、動作・参照可能なソースコードを公開させて頂いております。

それでは続きはまたにしましょう!

  1. はじめに
  2. cloudBitの情報取得
  3. cloudBitからの情報受け取り口
  4. 受け取ったデータをDBに格納
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?