GoogleAppsでアカウントでChromeの同期ができなくなった

デスクトップとノートでChromeの同期を使っているのですが、少し前にノートPCのWindowsがブルースクリーンで落ちてしまって、それ以降、「お客様のドメインでは同期サービスをご利用いただけません。」というメッセージでChromeの同期機能が使えなくなっていました。なにかデータが壊れたのか、単に再起動してChromeのアップデートがかかっただけなのかわかりません。

GoogleAppsのフォーラムを参考に、"ユーザ名%Appsドメイン名@gtempaccount.com"というアカウントで同期自体はできるようになりました。デスクトップの方は以前としてAppsのアカウントで同期できています。
状況からすると、GoogleAppsGoogleアカウントが統合された際に、アカウントが競合していたサービスうまく統合できていないようです。アカウント統合前からAppsで使っているアカウントでChromeの同期を使っていました。
ただ、Appsアカウントでダッシュボードにアクセスしても Chrome Syncという項目が存在しないので、どうしたものかという状態です。。

Android 2.3.3でのGalaxy SのGPS

遅ればせながら一ヶ月ほど前にGalaxy Sを2.3.3にアップデートしました。Androidのアップデート自体は非常に満足しています。2.3からレスポンスに関してはストレスなしで使えるレベルに達したように思います。
Galaxy Sは当初からGPSに不満がありいろいろ試してみても根本的には解決していなかったので、今回のアップデートに期待していたのですが、、結果的には2.2より症状が悪化しているようにみえます。現在位置の取得に時間がかかるのは相変わらずなのですが(この部分は多少改善しているようにもみえる)、それより困っているのは、

  • マップナビの使用中に頻繁にGPSを見失う
  • GPS Testなどで現在位置を取得しても他アプリでは再度現在位置を取得しようとする

カーナビとして使うには致命的でこのままではほぼ使い物になりません。
Galaxy S(SC-02B)のGPS - azuki note
2.2のころもGpsSetup2でGPSのパラメータをいじったりしてみたのですが、結局、GPS Testというアプリで一度現在位置を取得してから(ただし数分かかる)マップナビを起動すると安定して使えていました。
とりあえず2.3でもパラメータを変更してみました。GpsSetup2はAngryGPSという名前になっています。起動方法は電話から"*#*#3214789650#*#*"をダイヤルします。現在は以下のような設定で様子をみています*1


また、モバイルネットワークとWifiを無効にした状態でGPSで位置を検出するとよいという情報もあったので試してみました。
がらくたVol.9 : Galaxy s が2.3.3でGPSが使えな〜〜い!と、思わせて・・・
うーむ、効果があるようる気もしたのですが、、マップナビで画面上に"GPS 衛星を検出しています"と表示されないだけで、通知バーのGPSのアイコンが点滅しており、たまにGPSを見失っているように見えます。なんでこういう動作に変わったのか不思議なのですが。。

現在は上のスクリーンショットの設定とこの方法をあわせて使っていますが、完全に使える状態になったかわからないです。GPSの動作が安定しないので設定の変更に効果があったのか確証が持てないのが難しいところです。

GPS Testを使ってみると、In Viewの衛星の数はすぐに増えるのですが、In Useが増えるのに非常に時間がかかることが多いです。

GPSの性能が悪いという情報をみかけると、b-mobileのSIMを使っているケースが多いような気がするのですが、、ドコモショップのGalaxy S IIも似たようなものなので関係ないかもしれません。改善されないようであれば、Xperia acroあたりに買い替えるか、auの携帯をiPhoneに機種変更することも考えます。iOSだとマップナビが提供されていないのですが。。

*1:画像1枚目では"Operation Mode"が"MSBASED"になっていますが"STANDALONE"に設定しています。 Server Typeは"UMTS SLP"から"1X PDE"に変更しても再度設定を開くと元に戻ってしまう

Jerseyでアップロードされたファイルを処理する

サーバーサイド

Jerseyでマルチパート(multipart/form-data)によりアップロードされたファイルを処理するためには、jersey-multipartというモジュールを使用します。
pom.xmlのdependencyにjersey-multipartを追加します。

<dependencies>
	<!-- ... -->
	<dependency>
		<groupId>com.sun.jersey</groupId>
		<artifactId>jersey-server</artifactId>
		<version>1.9</version>
	</dependency>
	<dependency>
		<groupId>com.sun.jersey.contribs</groupId>
		<artifactId>jersey-multipart</artifactId>
		<version>1.9</version>
	</dependency>
</dependencies>

サーバサイドのコードは以下のようになります。アップロードされたファイルを標準出力に書き出すだけですが。

@Path("/files")
public class FileResource {
	@POST
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public void load(
			@FormDataParam("foo") InputStream is,
			@FormDataParam("bar") String bar) {
		System.out.println("foo:" + org.apache.commons.io.IOUtils.toString(is));
		System.out.println("bar:" + bar);
	}
}

fooというパラメータ名でアップロードされたファイルをInputStreamとして受け取ります。barは普通の文字列のフォーム値です。

クライアントサイド

テストのため作成したサービスにアクセスするクライアントを作成してみます。
pom.xmlのdependencyにjersey-clientとjersey-multipartを追加します。

<dependencies>
	<!-- ... -->
	<dependency>
		<groupId>com.sun.jersey</groupId>
		<artifactId>jersey-client</artifactId>
		<version>1.9</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>com.sun.jersey.contribs</groupId>
		<artifactId>jersey-multipart</artifactId>
		<version>1.9</version>
		<scope>test</scope>
	</dependency>
</dependencies>

テストコードは以下のようになります。

@Test
public void testUpload() throws Exception {
	Client c = Client.create();
	
	// "rest"はコンテキストパス
	WebResource r = c.resource("http://localhost:8080/rest/files");

	MultiPart multiPart = new MultiPart();
	// アップロードするファイル
	File f = new File("foo.txt");
	org.apache.commons.io.IOUtils.FileUtils.writeStringToFile(f, "foo1");
	FileDataBodyPart fileDataBodyPart = new FileDataBodyPart("foo", f);
	multiPart.bodyPart(fileDataBodyPart);
	// その他のフォーム値
	FormDataBodyPart formDataBodyPart = new FormDataBodyPart("bar", "bar1");
	multiPart.bodyPart(formDataBodyPart);

	ClientResponse response = r
		.header("Content-type", "multipart/form-data")
		.post(ClientResponse.class, multiPart);

	assertEquals(200, response.getStatus());
	System.out.println(response);
}

正常に動作していれば、サーバサイドでは、

foo: foo1
bar: bar1

クライアントサイドでは、

POST http://localhost:8080/rest/files returned a response status of 200 OK

と出力されます。

maven-assembly-pluginでSNAPSHOTバージョンの依存ライブラリ

maven-assembly-plugin は、依存ライブラリのjarファイル等をまとめて配布用のファイルを作成してくれるプラグインです。dependencyで指定したライブラリにSNAPSHOTのバージョンがある場合の動作で困った事があったので対応方法をメモしておきます。Mavenおよびプラグインのバージョンに依存する可能性があります。

現象としては、dependencyにSNAPSHOTバージョンのライブラリが存在する場合に、jarのマニフェストファイルの Class-Path で指定するファイル名は、moduleA-0.0.1-SNAPSHOT.jarという形式なのですが、maven-assembly-pluginがコピーするjarファイル名はmoduleA-0.0.1-20110906.041435-49.jarのようにタイムスタンプが含まれる形式になり、結果としてアプリケーションが起動しません。

マルチモジュール構成でmaven-assembly-pluginを使っているモジュールから別モジュール(moduleA)を参照しています。ただし、moduleAをローカルでビルドしている場合には"-SNAPSHOT"のjarがコピーされる問題ないのですが、リモートのあるmavenリポジトリからmoduleAのjarをダウンロードすると発生します。

対策としては、以下いずれかでよさそうです。

maven-assembly-pluginの設定での対応

maven-assembly-pluginのディスクリプタで、ファイル名のフォーマットを指定することができます。outputFileNameMappingです。バージョン部分を"${artifact.baseVersion}"にすることでタイムスタンプを含まない形式になります(デフォルトでは"${artifact.version}"でタイムスタンプを含みます)。

<dependencySets>
    <dependencySet>
        <outputFileNameMapping> ${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}</outputFileNameMapping>
        <!-- .... -->
    </dependencySet>
</dependencySets>

maven-jar-pluginの設定での対応

maven-jar-pluginのconfigurationにて、useUniqueVersionsを使って制御します。useUniqueVersions=trueにするとマニフェストの方でもタイムスタンプが含まれる形式になります。

<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.2</version>
<configuration>
	<finalName>${pom.artifactId}</finalName>
	<archive>
		<manifest>
			<!-- .... -->
			<useUniqueVersions>true</useUniqueVersions>
			<addClasspath>true</addClasspath>
			<classpathPrefix>lib</classpathPrefix>
		</manifest>
	</archive>
</configuration>

falseにするとmaven-assembly-pluginの方出"-SNAPSHOT"付きのファイル名でコピーしてくれるかと期待したのですが、そうではない様です。。

JerseyでJSONシリアライズ対象外のプロパティを指定する (2)

JerseyでJSONシリアライズ対象外のプロパティを指定する (1)
こちらの続きです。JAX-RS(Jersey)でJavaオブジェクトをJSON形式に変換する際に、@XmlTransientをつけて対象外のプロパティを指定しましたが、今回はJacksonMixInAnnotationsを使用する方法を試してみました。この方法ではシリアライズ対象オブジェクトにアノテーション等を追加することなく、変換対象や名前等を変更することができます。
例えば、以下のようなクラスをJSONに変換するとします。

public class Person {
	public String name;
	public String emailAddress;
	public int age;
	// ...
}

ここで、"age"は変換対象外とし"emailAddress"は"email"という名前に変換したいという場合には以下のようなMixinクラスを作成します。

abstract class PersonMixin {
	PersonMixin (@JsonProperty("email") String emailAddress, @JsonIgnore ("age") int h) { }
}

この例ではコンストラクタにアノテーションをつけていますが、abstractなgetter?を定義してそこにアノテーションをつけることもできますし、PersonMixin自体に@JsonIgnoreProperties等を付けてまとめて指定することもできます。

このMixinを使用するには以下のようにします。

ObjectMapper om = new ObjectMapper();
om.getSerializationConfig().addMixInAnnotations(Person.class, PersonMixin.class);
String str = om.writeValueAsString(obj);

個別のMixinを適用するのではなく、設定をまとめて管理するには、Moduleを使うことができます。

public class MyModule extends SimpleModule {
	public MyModule() {
		super("MyModule", new Version(0, 0, 1, "SNAPSHOT"));
	}

	@Override
	public void setupModule(SetupContext context) {
		super.setupModule(context);
		context.setMixInAnnotations(Person.class, PersonMixin.class);
	}
}

このModuleを使用するには以下のようにします。

ObjectMapper om = new ObjectMapper();
om.registerModule(new MyModule());
String str = om.writeValueAsString(obj);

JerseyでJacksonMixInAnnotationsを使用する

JerseyでJacksonMixInAnnotationsを使用する場合には、ObjectMapperを返すProviderを作成して登録する必要があります。Providerのクラスは以下のようになります。

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.ObjectMapper;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {

	private ObjectMapper mapper;

	public ObjectMapperProvider() {
		mapper = new ObjectMapper();
		mapper.registerModule(new MyModule());
	}

	@Override
	public ObjectMapper getContext(Class<?> type) {
        return mapper;
	}
}

ObjectMapperはConfigurationを変更しなければスレッドセーフということなのでインスタンスを使い回しています。
TomcatなどのJAX-RSに対応していないコンテナの場合にはApplicationクラスで登録します。

import javax.ws.rs.core.Application;

public class MyApplication extends Application {
	// ...

	@Override
	public Set<Object> getSingletons() {
		Set<Object> classes = new HashSet<Object>();
		classes.add(new ObjectMapperProvider());
		return classes;
	}
}

これでJSONの生成にこのObjectMapperが使用されるので、MyModuleでの指定が反映されます。

Selenium 2でChromeDriverを使う

WebアプリケーションのテストにSeleniumを使っていますが、少し前になりますがSelenium 2が正式にリリースされたので試しています。Selenium 2は、WebDriverベースとなりアーキテクチャ的にも大きく変更されています。Selenium RCで使用していたSeleniumServerは不要になっています。JUnitから使用する場合には、
Selenium 2.0 and WebDriver ― Selenium Documentation
このドキュメントにあるとおりMavenのdependencyにselenium-javaを追加し、

<dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>2.5.0</version>
</dependency>

テストコードで以下のようにブラウザを起動します。WebDriverそのままのAPIですね。

WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com");

これでFirefoxGoogleのページが表示されます。Chromeを使用する場合には、FirefoxDriverをChromeDriverに変更すればよいのですが、それだけだと以下のような例外が発生します。

java.lang.IllegalStateException: The path to the chromedriver executable must be set by the webdriver.chrome.driver system property; for more information, see http://code.google.com/p/selenium/wiki/ChromeDriver. The latest version can be downloaded from http://code.google.com/p/chromium/downloads/list
	at com.google.common.base.Preconditions.checkState(Preconditions.java:172)
	at org.openqa.selenium.chrome.ChromeDriverService.createDefaultService(ChromeDriverService.java:86)
	at org.openqa.selenium.chrome.ChromeDriver.(ChromeDriver.java:87)

Chromeの場合には、メセージにあるとおりChromiumのサイトから、別途、プラットフォーム毎の実行ファイルをダウンロードした上でそのパスをシステムプロパティで指定する必要があります。

System.setProperty("webdriver.chrome.driver","lib/chromedriver/win32/chromedriver.exe");
WebDriver driver = new ChromeDriver();

Seleniumのドキュメントは、これまでのSeleniumHQと元WebDriverのGoogleCodeのサイトに別れてしまっているので、ちょっと見づらいですね。ChromeDriverについては、こちらにあります。

AWS SDK for Rubyでインスタンスを起動/停止するスクリプト

AWS SDK for Rubyインスタンスをまとめて起動/停止するスクリプトを書きました。
特定の時間しか使わないインスタンスが複数あるのですが、これらをスケジュールにしたがってまとめて起動・停止するために、いままでCloudworksを使わせていただいていたのですが、スクリプトを書いてJenkinsから実行するようにしました。

まず、AWS SDK for Rubyのインストールですが、gemでインストールできます。Ubuntuの初期状態だと、他にいくつか必要なパッケージがありました。

sudo apt-get install ruby-dev
sudo apt-get install libxslt-dev libxml2-dev
sudo gem install aws-sdk

スクリプトは以下のようなものです。
複数のインスタンスをまとめて操作するために、あらかじめAWS Console等で対象のインスタンスに"Group"というTagでグループ名を指定しておきます。

# -*- coding: utf-8 -*-

require 'rubygems'
require 'yaml'

require 'aws-sdk'

COMMAND=["start", "stop", "list"]

unless ARGV.size == 2
  puts "Usage: #{$0} <target_group> <command(#{COMMAND.join('/')})>"
  exit 1
end

target_group = ARGV[0]
command = ARGV[1]
unless COMMAND.index(command)
  puts "command must be #{COMMAND.join(' or ')} !"
  exit 1
end

puts "#{command} instances in #{target_group}."

config = YAML.load(File.read("config.yml"))
AWS.config(config)

AWS::EC2.new.instances.tagged("Group", target_group).each{ |i|
  puts "#{i.id}(#{i.tags['Name']}) => #{i.status.to_s}"
  if command == 'start' && i.status == :stopped
    puts "start instance"
    i.start
  elsif command == 'stop' && i.status == :running
    puts "stop instance"
    i.stop
  end
}

実行にはスクリプトと同じフォルダにconfig.ymlというファイルを作成して認証情報とリージョンを指定します。

access_key_id: YOUR_ACCESS_KEY_ID
secret_access_key: YOUR_SECRET_ACCESS_KEY
ec2_endpoint: ec2.ap-northeast-1.amazonaws.com

"test"というグループのインスタンスを起動する場合には以下のように実行します。

ruby run_instance_by_group.rb test start

start instances in test.
i-XXXXXXXX(instance1) => stopped
start instance
i-XXXXXXXX(instance2) => stopped
start instance
...

stopにするとインスタンスは停止します。listはデバッグ用でインスタンスのライフサイクルを表示します。
インスタンスが"running"になるのに少し時間がかかるので("pendding")、直後にインスタンスを使って操作するのであれば、runningになるまで待つようにした方がいいかもしれません。