読者です 読者をやめる 読者になる 読者になる

割と普通なブログ

Microsoft Azure や ASP.NET、Java EE 系の話題を記載します

DocumentDB の Java SDK で jackson を使う場合の TIPS

随分前に発表された DocumentDB だが、意外にサンプルが少ないので触ってみたところを軽く記載する。DocumentDB は JSON 形式でデータをやり取りするため、Java オブジェクトを JSON 形式に変換する必要がある。

Build a Java web application using DocumentDB に記載されたサンプルを見ると Project Lombok を利用しているが、今回は jackson を使ってみた。

まず、あらかじめ管理ポータルから DocumentDB アカウントを作成し、"TestDB" というデータベースの作成、"TestCollection" というコレクションの作成、以下の様にドキュメントの作成を実施する。
f:id:waritohutsu:20150524151856p:plain

次に、管理ポータルからエンドポイントとキーを取得し、以下のコードを実行する。

package com.mydomain.documentdb;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.microsoft.azure.documentdb.ConnectionPolicy;
import com.microsoft.azure.documentdb.ConsistencyLevel;
import com.microsoft.azure.documentdb.Database;
import com.microsoft.azure.documentdb.Document;
import com.microsoft.azure.documentdb.DocumentClient;
import com.microsoft.azure.documentdb.DocumentClientException;
import com.microsoft.azure.documentdb.DocumentCollection;
import com.microsoft.azure.documentdb.RequestOptions;
import com.mydomain.model.Person;

public class SampleTest {

	// Define an id for your database and collection
	private static final String DATABASE_ID = "TestDB";
	private static final String COLLECTION_ID = "TestCollection";

	// documentdb.properties から情報を取得
	private static String END_POINT;
	private static String MASTER_KEY;

	private static DocumentClient documentClient;
	private static Database databaseCache;
	private static DocumentCollection collectionCache;

	@Before
	public void testInitialize() throws IOException {
		// TODO: 外部設定ファイルを作成すること
		String path = "documentdb.properties";
		InputStream in = SampleTest.class.getClassLoader().getResourceAsStream(
				path);
		if (in == null) {
			throw new IllegalArgumentException(path + " not found.");
		}
		Properties props = new Properties();
		props.load(in);

		END_POINT = props.getProperty("END_POINT");
		MASTER_KEY = props.getProperty("MASTER_KEY");
		documentClient = new DocumentClient(END_POINT, MASTER_KEY,
				ConnectionPolicy.GetDefault(), ConsistencyLevel.Session);
	}

	@Test
	public void readTest01() throws DocumentClientException,
			JsonParseException, JsonMappingException, IOException {
		Person expected = new Person();
		Person actual;

		expected.setAge(45);
		expected.setName("割と普通");

		List<Document> documentList = documentClient
				.queryDocuments(getTestCollection().getSelfLink(),
						"SELECT * FROM root r WHERE r.id='" + 1 + "'", null)
				.getQueryIterable().toList();
		ObjectMapper mapper = new ObjectMapper();
		actual = mapper.readValue(documentList.get(0).toString(), Person.class);

		Assert.assertEquals(expected, actual);
	}

	private Database getTestDBDatabase() {
		if (databaseCache == null) {
			// Get the database if it exists
			List<Database> databaseList = documentClient
					.queryDatabases(
							"SELECT * FROM root r WHERE r.id='" + DATABASE_ID
									+ "'", null).getQueryIterable().toList();

			if (databaseList.size() > 0) {
				// Cache the database object so we won't have to query for it
				// later to retrieve the selfLink.
				databaseCache = databaseList.get(0);
			} else {
				// Create the database if it doesn't exist.
				try {
					Database databaseDefinition = new Database();
					databaseDefinition.setId(DATABASE_ID);

					databaseCache = documentClient.createDatabase(
							databaseDefinition, null).getResource();
				} catch (DocumentClientException e) {
					// TODO:
					e.printStackTrace();
				}
			}
		}

		return databaseCache;
	}

	private DocumentCollection getTestCollection() {
		if (collectionCache == null) {
			// Get the collection if it exists.
			List<DocumentCollection> collectionList = documentClient
					.queryCollections(
							getTestDBDatabase().getSelfLink(),
							"SELECT * FROM root r WHERE r.id='" + COLLECTION_ID
									+ "'", null).getQueryIterable().toList();

			if (collectionList.size() > 0) {
				// Cache the collection object so we won't have to query for it
				// later to retrieve the selfLink.
				collectionCache = collectionList.get(0);
			} else {
				// Create the collection if it doesn't exist.
				try {
					DocumentCollection collectionDefinition = new DocumentCollection();
					collectionDefinition.setId(COLLECTION_ID);

					// Configure the new collection performance tier to S1.
					RequestOptions requestOptions = new RequestOptions();
					requestOptions.setOfferType("S1");

					collectionCache = documentClient.createCollection(
							getTestDBDatabase().getSelfLink(),
							collectionDefinition, requestOptions).getResource();
				} catch (DocumentClientException e) {
					// TODO:
					e.printStackTrace();
				}
			}
		}

		return collectionCache;
	}
}
package com.mydomain.model;

public class Person {
	int age;
	String name;

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Person == false)
			return false;

		Person p = (Person) obj;
		boolean isAgeSame = age == p.getAge();
		boolean isNameSame = name == null ? p.getName() == null : //
				name.equals(p.getName());
		return isAgeSame && isNameSame;
	}
}

上記を実行すると、以下のエラーが発生する。

org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "_attachments" (Class com.mydomain.model.Person), not marked as ignorable
 at [Source: java.io.StringReader@1bb5a082; line: 1, column: 18] (through reference chain: com.mydomain.model.Person["_attachments"])
	at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
	at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:246)
	at org.codehaus.jackson.map.deser.StdDeserializer.reportUnknownProperty(StdDeserializer.java:604)
	at org.codehaus.jackson.map.deser.StdDeserializer.handleUnknownProperty(StdDeserializer.java:590)
	at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:689)
	at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:514)
	at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:350)
	at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2395)
	at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1595)
	at com.mydomain.documentdb.SampleTest.readTest01(SampleTest.java:61)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	(中略)


不明なフィールドである _attachments が存在する様だ。念のためやり取りされる JSON データを見ると以下の様に複数のフィールドが追加されている。

{_attachments=attachments/, _rid=i-ZOANFBawACAAAAAAAAAA==, name=割と普通, id=1, _self=dbs/i-ZOAA==/colls/i-ZOANFBawA=/docs/i-ZOANFBawACAAAAAAAAAA==/, age=45, _etag="00001000-0000-0000-0000-5560769f0000", _ts=1432385183}

こちらの問題に対応するには以下の様に jackson 特有のアノテーションを追加して不明なプロパティの無視を指定する。

package com.mydomain.model;

import org.codehaus.jackson.annotate.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
	int age;
	String name;

	//(中略)


読み込みの場合は上記で良いのだが、書き込みの場合(特に更新)では今回無視したフィールドが必要なことが懸念される。こちらに対する調査はまた後日で。

Maven でコンパイルするとラムダ式使えないと怒られる件

JDK 1.8 が出てだいぶ経つので、各位もラムダ式を使っていることだと思う。今回はラムダ式を利用したモジュールで Maven コンパイルを実施する場合の軽い TIPS を紹介する。ご存じな方はご存じだと思うが、毎度 pom.xml に記載用の情報を検索するのも面倒なので以下に記載しておきたい。

ハマるポイントとして、ラムダ式を利用したソースで "mvn compile" 等を実行した場合に以下のエラーが発生する点だ。

D:\opt\workspace\Java8CodingProject>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Java8CondigProject 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
<中略>
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /D:/opt/workspace/Java8CodingProject/src/test/java/com/mydomain/lambda/SampleTest.java:[18,33] ラムダ式は-source 1.5でサポートされていません  (ラムダ式を使用可能にするには、-source 8以上を使用してください)
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.798 s
[INFO] Finished at: 2015-05-23T16:58:51+09:00
[INFO] Final Memory: 10M/491M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.
1:testCompile (default-testCompile) on project Java8CondigProject: Compilation failure
[ERROR] /D:/opt/workspace/Java8CodingProject/src/test/java/com/mydomain/lambda/SampleTest.java:[18,33] ラムダ式は-source 1.5でサポートされていません
[ERROR] (ラムダ式を使用可能にするには、-source 8以上を使用してください)
[ERROR] -> [Help 1]

上記のエラーを見れば一目瞭然だが、Maven さん(使っているのは 3.2.5 )は JDK 1.5 ベースでコンパイルしているらしい(流石に古すぎだろうと思うが)。。。。


解決方法は以下の様に pom.xml に追記すればよい。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mydomain</groupId>
	<artifactId>Java8CondigProject</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
	<dependencies>
		<dependency>
        (中略)

次回以降は 1.8 ベースでのコンパイルが実行される。

Windows Azure Storage Emulator のインストールに失敗する場合の TIPS

Web Platform Installer 等を利用して Azure SDK のセットアップをする際、Windows Azure Storage Emulator のインストールに失敗することがある。今回はこちらを解決するかもしれない TIPS を紹介する。今回の作業による影響範囲は見切っていないので、実施は自己責任でお願いしたい

概要

Web Platform Installer 等を利用してインストールに失敗した場合、エラーダイアログからログを閲覧することができる。インストールの失敗ログを開き、以下のエラーが存在しているかを確認する。

<中略>
Action start 11:02:11: InstallFinalize.
CAQuietExec:  Windows Azure Storage Emulator 3.4.0.0 command line tool
CAQuietExec:  Error: Cannot create database.
CAQuietExec:  Error 0xfffffff3: Command line returned an error.
CAQuietExec:  Error 0xfffffff3: CAQuietExec Failed
CustomAction RunInitialize returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
Action ended 11:02:12: InstallFinalize. Return value 3.
Action ended 11:02:12: INSTALL. Return value 3.
Property(S): UpgradeCode = {CF5CD495-AEDE-42DA-B7CF-A70D398D4E6A}
<中略>

上記を見ると、どうやらデータベースを作成できないらしい。ご存知の方もいるかもしれないが、Azure のエミュレータはローカルにデータベースを持つので、そちらが再作成がうまくいっていないようだ。
こちらのエラーで検索すると MSDN フォーラムで "CAQuietExec: Error: Cannot create database." の記事を見つけた。いろいろと議論がある様だが、ポイントは以下だ。

Hi,

That may not be sufficient.

As far as I'm concerned, my first install attemp left some files in my c:\users\[user] directory :

WAStorageEmulatorDb30.mdf
WAStorageEmulatorDb30_log.ldf

The sqllocaldb logs would mention these files still existed and it would not erase them.

I just deleted them manually.
Recreated the v11.0 (without the double quotes, please):

sqllocaldb stop v11.0
sqllocaldb delete v11.0
sqllocaldb create v11.0

私の場合は C:\users\[user directory] に以下のファイルが存在したので、すべて削除(別フォルダに移動)した。

  • WAStorageEmulatorDb33.mdf
  • WAStorageEmulatorDb33_log.ldf
  • WAStorageEmulatorDb34.mdf
  • WAStorageEmulatorDb34_log.ldf

次にコマンドラインを開き(管理者権限は不要)、以下のコマンドを実行した。

sqllocaldb stop v11.0
sqllocaldb delete v11.0
sqllocaldb create v11.0

後は再度 Azure SDK のインストールを実行すれば問題なくインストールが完了すると思われる。

Azure Redis Cache を Java で利用する

Azure Redis Cache が日本でも利用可能になってしばらくたつが、確認のために Java から疎通を取ったので簡単にメモ。

使うまでの手順

Azure の新ポータル側 https://portal.azure.com にアクセスし、Data + Storage から Redis Cache を選択する。
f:id:waritohutsu:20150321191729j:plain

次に設定からアクセスポートを選択し「SSL によるアクセスのみ許可する」で「いいえ」を選択する。これは Redis の Java クライアントである Jedis が SSL 接続をサポートしていないためだ。
f:id:waritohutsu:20150321192134j:plain

管理ポータルの設定は以上なので、次は Java プロジェクトを作成し、pom.xml に以下を追記して Jedis の依存関係を解決する。

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.6.2</version>
        </dependency>

以下のコードを利用して Azure Redis Cache を利用できる。

package com.domain.redisclientproject;

import redis.clients.jedis.Jedis;

public class Main {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("<site name>.redis.cache.windows.net", 6379);
        jedis.auth("<管理ポータルから取得できるキー>");
        jedis.set("foo", "bar");
        String value = jedis.get("foo");
        System.out.println(value);
    }
}

疎通までとなるが、上記の様に簡単に利用することができるのが分かる。

Application Insight の Java モジュールの拡張ポイントを弄ってみる

前回の記事で Java アプリケーション向けに Application Insight を動作させる方法を紹介したが、こちらに対してイベント処理を追加することができる。拡張に利用することのできるクラスは以下の二つだ。

  • applicationinsights-core-0.9.1.jar に含まれる TelemetryModule.java
  • applicationinsights-web-0.9.1.jar に含まれる WebTelemetryModule.java

拡張ポイントについて

TelemetryModule.java と WebTelemetryModule.java はそれぞれインターフェースであり、コードは以下になる。

package com.microsoft.applicationinsights.extensibility;
import com.microsoft.applicationinsights.TelemetryConfiguration;
public abstract interface TelemetryModule
{
  public abstract void initialize(TelemetryConfiguration paramTelemetryConfiguration);
}
package com.microsoft.applicationinsights.web.extensibility.modules;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract interface WebTelemetryModule
{
  public abstract void onBeginRequest(ServletRequest paramServletRequest, ServletResponse paramServletResponse);
  
  public abstract void onEndRequest(ServletRequest paramServletRequest, ServletResponse paramServletResponse);
}

眺めて頂けば察しがつくと思うが、モジュール初期化時、リクエスト前後で処理が追加できることが分かる。

拡張コードと設定

以下のクラスを作成した。

package com.mydomain.applicationinsightmodule;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.extensibility.TelemetryModule;
import com.microsoft.applicationinsights.web.extensibility.modules.WebTelemetryModule;

public class MyTelemetryModule implements WebTelemetryModule, TelemetryModule {

	@Override
	public void initialize(TelemetryConfiguration initializer) {
		System.out.println("@@@@InstrumentationKey="
				+ initializer.getInstrumentationKey());
	}

	@Override
	public void onBeginRequest(ServletRequest paramServletRequest,
			ServletResponse paramServletResponse) {
		System.out
				.println("@@@@onBeginRequest at " + this.getClass().getName());
	}

	@Override
	public void onEndRequest(ServletRequest paramServletRequest,
			ServletResponse paramServletResponse) {
		System.out.println("@@@@onEndRequest at " + this.getClass().getName());
	}

}

さらに ApplicationInsights.xml に設定を追記した。

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ApplicationInsights
	xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings"
	schemaVersion="2014-05-30">
	<InstrumentationKey><自分のキー>
	</InstrumentationKey>
	<ContextInitializers>
	</ContextInitializers>
	<TelemetryInitializers>
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationIdTelemetryInitializer" />
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationNameTelemetryInitializer" />
	</TelemetryInitializers>
	<TelemetryModules>
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.modules.WebRequestTrackingTelemetryModule" />
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.modules.WebSessionTrackingTelemetryModule" />
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.modules.WebUserTrackingTelemetryModule" />
		<Add type="com.mydomain.applicationinsightmodule.MyTelemetryModule" />
	</TelemetryModules>
	<Channel>
		<!-- Setting DeveloperMode to true will enable immediate transmission of 
			the telemetry events, which can be helpful during the development process. 
			Make sure to turn this off on production servers due to performance considerations. -->
		<DeveloperMode>false</DeveloperMode>
	</Channel>
	<DisableTelemetry>false</DisableTelemetry>
</ApplicationInsights>

拡張が完了したので、Java Web アプリケーションを起動すると以下の様にログが出力されていることが確認できる(ホントは slf4j とか使った方がいいのは置いといて)。

情報: Starting Servlet Engine: Apache Tomcat/8.0.9
3 15, 2015 12:47:44 午後 org.apache.jasper.servlet.TldScanner scanJars
情報: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
@@@@InstrumentationKey=<自分のキー>
3 15, 2015 12:47:44 午後 org.apache.coyote.AbstractProtocol start
情報: Starting ProtocolHandler ["http-nio-8080"]
3 15, 2015 12:47:44 午後 org.apache.coyote.AbstractProtocol start
情報: Starting ProtocolHandler ["ajp-nio-8009"]
3 15, 2015 12:47:44 午後 org.apache.catalina.startup.Catalina start
情報: Server startup in 1987 ms
@@@@onBeginRequest at com.mydomain.applicationinsightmodule.MyTelemetryModule
@@@@onEndRequest at com.mydomain.applicationinsightmodule.MyTelemetryModule
@@@@onBeginRequest at com.mydomain.applicationinsightmodule.MyTelemetryModule
@@@@onEndRequest at com.mydomain.applicationinsightmodule.MyTelemetryModule

Application Insight を Java アプリケーションから利用する

アプリケーションのモニタリングやアラートを設定することが可能な Application InsightsJava からも利用可能になったのでさっそく試してみた。

利用するまでの流れ

まずは以下の様に Eclipse Plugin の Application Insight 機能をインストールする。
f:id:waritohutsu:20150315121832p:plain

次に、以下の様に Azure の新管理ポータルで Application Insight を新規に作成した後、setting - プロパティ から Instrumentation Key を取得する。
f:id:waritohutsu:20150315122236p:plain

次は Eclipse 上から Dynamic Java Web アプリケーションを作成し、右クリックメニューから Configure Application Insight を選択して Application Insight有効化する。この際、Instrumentation Key を求められるので管理ポータルから取得しておくのを忘れないこと。
f:id:waritohutsu:20150315122513p:plain

Application Insight 有効化すると、web.xml に以下の記載が追加されている。以下を見ると単なるサーブレットフィルタなので、Websites 等に配置せずとも動くことが推察できる。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="WebApp_ID" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>

	<filter>
		<filter-name>ApplicationInsightsWebFilter</filter-name>
		<filter-class>com.microsoft.applicationinsights.web.internal.WebRequestTrackingFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>ApplicationInsightsWebFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>

更にソースフォルダに ApplicationInsights.xml が配置されており、以下の記載がされている。こちらで Application Insight を構成する情報が設定されていることが分かる(TelemetryInitializers タグにクラスを追加すると何かしてくれそうな気もする)。

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ApplicationInsights
	xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings"
	schemaVersion="2014-05-30">
	<InstrumentationKey><自分のキーが書いてある>
	</InstrumentationKey>
	<ContextInitializers>
	</ContextInitializers>
	<TelemetryInitializers>
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationIdTelemetryInitializer" />
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationNameTelemetryInitializer" />
	</TelemetryInitializers>
	<TelemetryModules>
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.modules.WebRequestTrackingTelemetryModule" />
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.modules.WebSessionTrackingTelemetryModule" />
		<Add
			type="com.microsoft.applicationinsights.web.extensibility.modules.WebUserTrackingTelemetryModule" />
	</TelemetryModules>
	<Channel>
		<!-- Setting DeveloperMode to true will enable immediate transmission of 
			the telemetry events, which can be helpful during the development process. 
			Make sure to turn this off on production servers due to performance considerations. -->
		<DeveloperMode>false</DeveloperMode>
	</Channel>
	<DisableTelemetry>false</DisableTelemetry>
</ApplicationInsights>

この時点でも動作はするが Application Insight のクイックスタートの画面から以下の JavaScript が取得できるので、こちらを JSP 画面 or JSF 画面等の監視をしたいページに張り付ける。

<!--
ご使用のアプリケーションに関するエンド ユーザー利用状況分析を収集するには、
追跡するページごとに以下のスクリプトを挿入してください。
このコードを、</head> 終了タグの直前に、
他のスクリプトより前の位置に配置します。最初のデータは、数秒後に
自動的に表示されます。
-->
<script type="text/javascript">
    var appInsights=window.appInsights||function(config){
        function s(config){t[config]=function(){var i=arguments;t.queue.push(function(){t[config].apply(t,i)})}}var t={config:config},r=document,f=window,e="script",o=r.createElement(e),i,u;for(o.src=config.url||"//az416426.vo.msecnd.net/scripts/a/ai.0.js",r.getElementsByTagName(e)[0].parentNode.appendChild(o),t.cookie=r.cookie,t.queue=[],i=["Event","Exception","Metric","PageView","Trace"];i.length;)s("track"+i.pop());return config.disableExceptionTracking||(i="onerror",s("_"+i),u=f[i],f[i]=function(config,r,f,e,o){var s=u&&u(config,r,f,e,o);return s!==!0&&t["_"+i](config,r,f,e,o),s}),t
    }({
        instrumentationKey:"<自分のキーを利用する>"
    });
    
    window.appInsights=appInsights;
    appInsights.trackPageView();
</script>

上記を利用してローカル等で Tomcat を起動して動作を確認すると、無事情報が取得できていることが分かる。
f:id:waritohutsu:20150315123649p:plain

WindowsAzureToolkitForEclipseWithJava / StarterKit / CSPackAntTask を修正する

Microsoft Azure のクラウドサービス向けの Eclipse Plugin として Windows Azure Toolkit For Eclipse With Java があるのはご存知だと思う(未だに Windows Azure なのは置いといて)。こちらを利用して Eclipse 上からリモートデスクトップ、セッションアフィニティ、デプロイするロース数の設定等が可能だ。

ずいぶん進化が進んだもので、JBoss AS, Tomcat, Jetty はもちろん WebSphere も対応したそうだが、The Azure Toolkit for Eclipse now Includes WebSphere Liberty and Application InsightsIBM 社の WebSphere にも対応したらしいが、試してみたところうまく動かなかったので自身でプラグインをリコンパイルして確認をしてみた。ノウハウを死蔵してもしかたないので、今回は Windows Azure Toolkit For Eclipse With Java のリコンパイルの TIPS を紹介する。

修正箇所/内容

今回私が修正した個所は WindowsAzureToolkitForEclipseWithJava / StarterKit / CSPackAntTask であり、Eclipse プロジェクト内に .cspack.jar という隠しファイル名で格納されている個所だ。ソースコードを確認したところpackage.xml 設定ファイルから *.cspkg ファイルを作成する処理を担っている様だ。
こちらに対して「*.cspkg を作成する際の引数をログとして出力する処理」を追加する。

まずは Github から以下の定番コマンドでソースコードを取得する。

>git clone https://github.com/MSOpenTech/WindowsAzureToolkitForEclipseWithJava.git

上記に CSPackAntTask が含まれているので、Eclipse の既存プロジェクトのインポートから WindowsAzureToolkitForEclipseWithJava\StarterKit\CSPackAntTask を選択する。

ソースコードをインポート後、ビルドが通らないがどうやら org.apache.ant と com.microsoft.azure が足りないようなので、以下の pom.xml をダミーで作成して mvn eclipse:eclipse で依存 jar を取得する*1

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.microsoft.azure</groupId>
	<version>0.7.0</version>
	<artifactId>azure-cspack-anttask</artifactId>
	<packaging>jar</packaging>

	<dependencies>
		<dependency>
			<groupId>org.apache.ant</groupId>
			<artifactId>ant</artifactId>
			<version>1.8.2</version>
		</dependency>
		<dependency>
			<groupId>com.microsoft.azure</groupId>
			<artifactId>azure-storage</artifactId>
			<version>2.0.0</version>
		</dependency>
	</dependencies>
</project>

次に *.cspack ファイル作成時のコマンド引数をログとして出力する処理を追加するため com.microsoftopentechnologies.windowsazure.tools.build.WindowsAzurePackage.java に以下の様に処理を追加する。ソースコードを見ればわかるが、中身に cspack.exe を利用している様だ。

	public void execute() throws BuildException {
		// <中略>

		// Run cspack.exe
		this.log("Starting package generation...");
		try {
			if (getSdkDir() != null) {
				// Get cspack.exe cmd-line
				List<String> csPackCmdLine = this.createCSPackCommandLine();

				//追加処理ここから
 				for (int i = 1; i < csPackCmdLine.size(); i++) {
					this.log("arg" + i + ": " + csPackCmdLine.get(i));
				}
				//追加処理ここまで

				this.runCommandLine(csPackCmdLine);
			} else {
				throw new IllegalArgumentException(
						"Azure SDK not found, cannot build non-CTP package");
			}
		} catch (Exception e) {
			reportBuildError(e);
		}

修正後、Ant を利用して備え付けの build.xml をたたくことで Eclipse プロジェクト内の jar フォルダに .cspack.jar が作成される。この時点での Eclipse プロジェクトの Package Explorer は以下のようになる。

最後に、CSPackAntTask\jar\.cspack.jar フォルダを Azure Eclipse プロジェクト内に含まれる .cspack.jar に上書きしてクラウドサービス上へのデプロイを試すと以下の様に追加処理のログが出力されている*2

Buildfile: D:\opt\workspace\HelloAzure\package.xml

checkResetScript:

resetEmulator:

waitForReset:

createwapackage:
       [mkdir] Created dir: D:\opt\workspace\HelloAzure\deploy
      [delete] Deleting directory D:\opt\workspace\HelloAzure\deploy.old
[windowsazurepackage] Verified attributes.
[windowsazurepackage] Role "WorkerRole1": Verifying the approot "D:\opt\workspace\HelloAzure\WorkerRole1\approot"
[windowsazurepackage] Role "WorkerRole1": Importing components...
[windowsazurepackage] 	Imported as 'jdk1.8.0_25' from "C:\Program Files\Java\jdk1.8.0_25"
[windowsazurepackage] 	Imported as 'jboss-as-7.1.1.Final.zip' from "D:\opt\jboss\jboss-as-7.1.1.Final"
[windowsazurepackage] Role "WorkerRole1": Finished importing components
[windowsazurepackage] Role "WorkerRole1": Generating component deployment script...
[windowsazurepackage] Role "WorkerRole1": Created internal startup script
[windowsazurepackage] Starting package generation...
[windowsazurepackage] arg1: D:\opt\workspace\HelloAzure\ServiceDefinition.csdef
[windowsazurepackage] arg2: /role:WorkerRole1;D:\opt\workspace\HelloAzure\WorkerRole1\approot
[windowsazurepackage] arg3: /rolePropertiesFile:WorkerRole1;D:\opt\workspace\HelloAzure\.rolePropertiesOS3
[windowsazurepackage] arg4: /out:D:\opt\workspace\HelloAzure\deploy\WindowsAzurePackage.cspkg
[windowsazurepackage] Executing '[C:\Program Files\Microsoft SDKs\Azure\.NET SDK\v2.5\bin\cspack.exe, D:\opt\workspace\HelloAzure\ServiceDefinition.csdef, /role:WorkerRole1;D:\opt\workspace\HelloAzure\WorkerRole1\approot, /rolePropertiesFile:WorkerRole1;D:\opt\workspace\HelloAzure\.rolePropertiesOS3, /out:D:\opt\workspace\HelloAzure\deploy\WindowsAzurePackage.cspkg]'...
[windowsazurepackage] Process started
[windowsazurepackage] Microsoft(R) Azure(TM) Packaging Tool version 2.5.0.0
[windowsazurepackage] for Microsoft(R) .NET Framework 4.0
[windowsazurepackage] Copyright c Microsoft Corporation. All rights reserved.
[windowsazurepackage] D:\opt\workspace\HelloAzure\ServiceDefinition.csdef: Warning  CloudServices040 : The 'schemaVersion' attribute is unspecified. Please set the attribute to avoid this warning.

このように必要な場合はソースを修正して動作を確認することがご理解いただけたと思う。

*1:ここの pom.xml は公開してほしいところ

*2: Executing 行の ... 部分が気になったので処理を追加したが、特に見切れてたのはなさそう