2015年10月15日木曜日

Cygwin でパス指定時に No such file or directory

Cygwin 上で突如パス指定が No such file or directory とエラーが出て嵌った・・・

ホームディレクトリから指定した場合
ls ~/.bash_profile
/bin/ls: /cygdrive/d/home/.bash_profile: No such file or directory
絶対パス
ls /cygdrive/d/.bash_profile
/bin/ls: /cygdrive/d/home/.bash_profile: No such file or directory
カレントディレクトリ
ls ./.bash_profile
./.bash_profile
どうやら相対パスの場合見えるようだ

次に cygdrive を見てみると
ls /cygdrive/
/bin/ls: /cygdrive/: No such file or directory
そもそもこれがみえない・・・
今朝方、Windows Update と Norton のインストールを行ったためアンインストールしてみる。
が、変わらない・・・
その他、/usr 以下に作っていたディレクトリも見えなくなっている・・・
そこで / から追ってみようと ls を叩いてみると
ls /
Git Bash.vbs
README.portable
ReleaseNotes.rtf
bin
cmd
doc
etc
git-bash.bat
git-cheetah
git-cmd.bat
lib
libexec
share
ssl
ん? /usr /var 等が見当たらない。
そして git の文字が・・・
そういえば、最近 MSysGit に PATH を通した記憶が。
エクスプローラで MSysGit のディレクトリを見てみると同じフォルダ構成になっている。
PATH を消してみるとなおった

どうやら PATH を .bash_profile に設定した後、再読み込みも再起動もしていなかったらしい・・・
MSysGit にある bin の以下にコマンドがありこちらを使用していた模様。

2015年4月7日火曜日

System properties を sbt run 時に切り替え

Typesafe Config の設定ファイルを sbt run 時に指定したく、"-Dconfig.file=develo.conf" の指定時に嵌った・・・

System properties の切り替えのみの構成で、

./
 |
 +--build.sbt
 |
 +--project
 |  |
 |  +--build.properties
 |
 +--sampleconfig1
 |  |
 |  +--build.sbt
 |  |
 |  +--src
 |     |
 |     +--main
 |        |
 |        +--scala
 |           |
 |           +--SampleConfig1.scala
 |
 +--sampleconfig1.conf
 |
 +--sampleconfig2
 |  |
 |  +--build.sbt
 |  |
 |  +--src
 |     |
 |     +--main
 |        |
 |        +--scala
 |           |
 |           +--SampleConfig2.scala
 |
 +--sampleconfig2.conf

./build.sbt
name := "sampleconfig"

lazy val sampleconfig1 = project

lazy val sampleconfig2 = project

./project/build.properties
sbt.version=0.13.8

./sampleconfig1/build.sbt
name := "sampleconfig1"

./sampleconfig2/build.sbt
name := "sampleconfig2"

./sampleconfig1/src/main/scala/SampleConfig1.scala
object SampleConfig1 extends Application {
  println("SampleConfig1: " + System.getProperty("config.file"))
}

./sampleconfig2/src/main/scala/SampleConfig2.scala
object SampleConfig2 extends Application {
  println("SampleConfig2: " + System.getProperty("config.file"))
}

まずは sammpleconfig1 から、引数指定で試す。

# sbt
> project sampleconfig1
> run -Dconfig.file=sampleconfig1.conf
SampleConfig1: null

取れない・・・
つぎに、javaOptions を指定。

 ./sampleconfig1/build.sbt
name := "sampleconfig1"

javaOptions in Runtime += "-Dconfig.file=sampleconfig1.conf"

取れない・・・
 ぐぐってみると、initialize を使ってる書き込みがあったので使用してみる。

 ./sampleconfig1/build.sbt
name := "sampleconfig1"

initialize ~= { _ =>
  if (new java.io.File("sampleconfig1.conf").exists)
    System.setProperty("config.file", "sampleconfig1.conf")
}

# sbt
> project sampleconfig1
> run
SampleConfig1: sampleconfig1.conf

おっ取れてる!
うまくいったということで暫くこれでやっていたが、設定ファイルが読めない時が・・・

実は initialize を使用した後に、sampleconfig2 のプロジェクトを追加していて

 ./sampleconfig2/build.sbt
name := "sampleconfig2"

initialize ~= { _ =>
  if (new java.io.File("sampleconfig2.conf").exists)
    System.setProperty("config.file", "sampleconfig2.conf")
}

# sbt
> project sampleconfig2
> run
SampleConfig2: sampleconfig2.conf

この時点では取れているのだが、再度 sampleconfig1 を実行すると

# sbt
> project sampleconfig1
> run
SampleConfig1: sampleconfig2.conf

と他のプロジェクトの設定が取れてしまっていた・・・

なので実行前に設定するタスクを入れることに。

 ./sampleconfig1/build.sbt
name := "sampleconfig1"

val setProperty = TaskKey[Unit]("setProperty") in Compile

setProperty := {
  if (new java.io.File("sampleconfig1.conf").exists)
    System.setProperty("config.file", "sampleconfig1.conf")
}

run in Compile <<= run in Compile dependsOn (setProperty)

 ./sampleconfig2/build.sbt
name := "sampleconfig2"

val setProperty = TaskKey[Unit]("setProperty") in Compile

setProperty := {
  if (new java.io.File("sampleconfig2.conf").exists)
    System.setProperty("config.file", "sampleconfig2.conf")
}

run in Compile <<= run in Compile dependsOn (setProperty)
# sbt
> project sampleconfig1
> run
SampleConfig1: sampleconfig1.conf
> project sampleconfig2
> run
SampleConfig2: sampleconfig2.conf

これでうまくいった

in Compile を in Runtime にするとうまくいかない・・・難しい・・・

2014年12月7日日曜日

Play Framework でデシリアライズ時に ClassNotFoundException

Play Framework ではセッションデータは Cookie に保存されるため、大きなデータは保存できない。
なので、Cookie には ID のみを保存して実際のデータはシリアリアズして DB に保存するように、
変更したところ嵌った・・・

Map や List など標準クラスでは問題ないのだが、独自クラスを保存すると ClassNotFoundException が発生する・・・

using(new ObjectInputStream(new ByteArrayInputStream(bytes))) { r =>
    r.readObject().asInstanceOf[HashMap[String, java.io.Serializable]]
  }
}
java.lang.ClassNotFoundException: com.example.MyClass
  at java.net.URLClassLoader$1.run(URLClassLoader.java:372) ~[na:1.8.0_20]
  at java.net.URLClassLoader$1.run(URLClassLoader.java:361) ~[na:1.8.0_20]
  at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_20]
  at java.net.URLClassLoader.findClass(URLClassLoader.java:360) ~[na:1.8.0_20]
  at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_20]
  at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_20]
  at java.lang.Class.forName0(Native Method) ~[na:1.8.0_20]
  at java.lang.Class.forName(Class.java:340) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:626) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371) ~[na:1.8.0_20]
どうもクラスローダが違うようだ。

調べてみたところ、似たような質問が上がっていて、 解決策が出ていた。
class LocalInputStream(in: InputStream) extends ObjectInputStream(in) {
  override protected def resolveClass(desc: ObjectStreamClass): Class[_] =
    Class.forName(desc.getName, false, this.getClass.getClassLoader)
}
using(new LocalInputStream(new ByteArrayInputStream(bytes))) { r =>
    r.readObject().asInstanceOf[HashMap[String, java.io.Serializable]]
  }
}
これでデシリアライズできるようになった。

2013年11月9日土曜日

ArchLinux の open-vm-tools を使用して VMware Player の hgfs による共有設定

CentOS をゲスト OS にする場合、よく VMware Player の共有を行うので、ArchLinux でもやろうとして嵌った・・・

とりあえず、公式 Wiki  を参考に開始した。

まず、ArchLinux の場合、vmware-tools でなく open-vm-tools を使用するようなので、インストール。

# pacman -S open-vm-tools

  • /etc/systemd/system/mnt-hgfs.mount
  • /etc/systemd/system/mnt-hgfs.automount
をコピペで作成。

/mnt/hgfs を作成。
# mkdir -p /mnt/hgfs

起動設定後再起動。
# systemctl enable mnt-hgfs.automount
# shutdown -r now 

確認してみると、エラー・・・
# ls /mnt/hgfs
ls: cannot access /mnt/hgfs/: No such device

マウントを確認。
# mount
[snip]
systemd-1 on /mnt/hgfs type autofs (rw,relatime,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
autofs になっている。

サイトをよく読むと、
共有フォルダを動かすには vmhgfs ドライバがロードされていなければなりません。
とのこと。
# lsmod | grep vmhgfs
存在しない・・・

サイトの上のほうに、open-vm-tools-modules に vmhgfs が含まれている記述があった。
よく読むべきだ・・・

探してみると
# pacman -Ss open-vm-tools-modules
community/open-vm-tools-dkms 5:2013.09.16-3
    kernel modules for the open source implementation of VMware Tools
名前が違うものが表示されるが・・・
とりあえずインストール。
# pacman -S open-vm-tools-dkms
[snip]
>>> Enable building of open-vm-tools modules:
>>> $ dkms add open-vm-tools/2013.09.16
>>>
>>> Enabling vmxnet driver in /usr/lib/modprobe.d/open-vm-tools-modules.conf
>>> (this will disable pcnet32 driver)
>>>
>>> If vmxnet driver doesn't handle your NIC, you have to manually
>>> disable loading of pcnet32 driver
>>>

表示されたコマンドを叩いてみる。
# dkms add open-vm-tools/2013.09.16
make: *** vmxnet: No such file or directory.  Stop.
make: *** vsock: No such file or directory.  Stop.

Creating symlink /var/lib/dkms/open-vm-tools/2013.09.16/source ->
                 /usr/src/open-vm-tools-2013.09.16

DKMS: add completed.

ソースにシンボリックリンクが貼られただけ?
まさかと思い、とりあえず再起動してみる。
# shutdown -r now
確認。
# ls /mnt/hgfs
ls: cannot access /mnt/hgfs/: No such device
# mount
[snip]
systemd-1 on /mnt/hgfs type autofs (rw,relatime,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
# lsmod | grep vmhgfs
# find / -name '*vmhgfs*'
/usr/bin/mount.vmhgfs
/usr/src/open-vm-tools-2013.09.16/vmhgfs
/usr/src/open-vm-tools-2013.09.16/vmhgfs/vmhgfs_version.h
・・・ 
ドライバのインストールなんぞめったにやらんしどうすんだ・・・とりあえずコンパイルするか・・・
# cd /usr/src/open-vm-tools-2013.09.16/vmhgfs/
# make
Using standalone build system.
Makefile:146: Makefile.normal: No such file or directory
make: *** No rule to make target 'Makefile.normal'.  Stop.
エラー箇所の Makefile を見てみると、Makefile.kernel と Makefile.normal を読み込み分けているっぽい。
Makefile.kernel は存在するので、VM_KBUILD を設定。
# make VM_KBUILD=yes
Using kernel build system.
make -C /lib/modules/3.11.6-1-ARCH/build/include/.. SUBDIRS=$PWD SRCROOT=$PWD/. \
  MODULEBUILDDIR= modules
make[1]: *** /lib/modules/3.11.6-1-ARCH/build/include/..: No such file or directory.  Stop.
Makefile:120: recipe for target 'vmhgfs.ko' failed
make: *** [vmhgfs.ko] Error 2
カーネルヘッダーがなさそうなのでインストールして再度コンパイル。
# pacman -S linux-headers
# make VM_KBUILD=yes
[snip]
/usr/src/open-vm-tools-2013.09.16/vmhgfs/inode.c: In function ‘HgfsPermission’:
/usr/src/open-vm-tools-2013.09.16/vmhgfs/inode.c:1893:29: error: ‘struct dentry’ has no member named ‘d_count’
          int dcount = dentry->d_count;
                             ^
scripts/Makefile.build:308: recipe for target '/usr/src/open-vm-tools-2013.09.16/vmhgfs/inode.o' failed
make[2]: *** [/usr/src/open-vm-tools-2013.09.16/vmhgfs/inode.o] Error 1
Makefile:1225: recipe for target '_module_/usr/src/open-vm-tools-2013.09.16/vmhgfs' failed
make[1]: *** [_module_/usr/src/open-vm-tools-2013.09.16/vmhgfs] Error 2
make[1]: Leaving directory '/usr/src/linux-3.11.6-1-ARCH'
Makefile:120: recipe for target 'vmhgfs.ko' failed
make: *** [vmhgfs.ko] Error 2
ググってみるとパッチがあるようで、
# curl https://raw.github.com/rasa/vmware-tools-patches/master/patches/vmhgfs/vmhgfs-d_count-kernel-3.11-tools-9.6.0.patch | patch -p1
[snip]
patching file inode.c
# make VM_KBUILD=yes
[snip]
make[1]: Entering directory '/usr/src/open-vm-tools-2013.09.16/vmhgfs'
make[1]: 'postbuild' is up to date.
make[1]: Leaving directory '/usr/src/open-vm-tools-2013.09.16/vmhgfs'
cp -f vmhgfs.ko ./../vmhgfs.o
成功したようだ。
insmod とか modprobe だったよなと思い調べつつ適当に進めていく。
とりあえず、モジュールをコピー。
# cd /usr/lib/modules/3.11.6-1-ARCH/kernel/fs
# mkdir vmhgfs
# cat /usr/src/open-vm-tools-2013.09.16/vmhgfs/vmhgfs.ko | gzip > vmhgfs/vmhgfs.ko.gz
modules.dep に追記。
# cd ../../
echo kernel/fs/vmhgfs/vmhgfs.ko.gz: >> modules.dep
modules.dep.bin 再生成後読み込み
# depmod -a
# modprobe vmhgfs
起動時に読み込むように /etc/modules-load.d/open-vm-tools.conf 作成
# echo vmhgfs >> /etc/modules-load.d/open-vm-tools.conf

これで、/mnt/hgfs の下にマウントされた共有ディレクトリがみれるようになった。
が、ファイルを置いてみたりしているとファイルによっては途中までしか読み込まない・・・
使えないんだろうか・・・ 

2013年9月2日月曜日

Java でフルカラー画像をグレースケールに変換

Java でフルカラー Jpeg を 8bit グレースケールの PNG に変換した際に嵌った記録・・・

元となる画像(1024 x 768)は、
これから、8bit グレースケールの PNG を生成することとする。

共通処理として、

画像読み込み

public static BufferedImage loadImage(String filename) {
  try (FileInputStream in = new FileInputStream(filename)) {
    return ImageIO.read(in);
  }
}

画像出力

public static void outputImage(String filename, BufferedImage image) throws IOException {
  ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next();
  writer.setOutput(ImageIO.createImageOutputStream(new File(filename)));
  writer.write(image);
  writer.dispose();
}
の 2 つを用意。

変換処理として、書き出し時に JPEGImageWriteParam の PNG 版のようなもので指定して変換できるかと、探したが見つからなかった。
com.sun.imageio.plugins.png 以下にいくつか PNG ようのクラスがあるようだ。
が、よく分からなかったので、タイプ指定で TYPE_BYTE_GRAY を指定した BufferedImage を新たに生成。
それにデータをコピーすることに。

変換処理は、2 パターン。

パターン1Graphics2D#drawImage() を使用

public static BufferedImage grayScale1(BufferedImage image) throws Exception {
  int w = image.getWidth(), h = image.getHeight();
  BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
  Graphics2D g2d = output.createGraphics();
  g2d.drawImage(image, 0, 0, w, h, null);
  return output;
}

パターン 2BufferedImage#setData() を使用

public static BufferedImage grayScale2(BufferedImage image) throws Exception {
  int w = image.getWidth(), h = image.getHeight();
  BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
  output.setData(image.getData());
  return output;
}

パターン1、パターン2 をそれぞれ呼び出し

try {
  BufferedImage org = loadImage("org.jpg");
  outputImage("output1.png", grayScale1(org));
  outputImage("output2.png", grayScale2(org));
} catch (Exception e) { e.printStackTrace(); } 

パターン1はいい感じ。
パターン2はデカッ
色が減った分、横に4倍引き伸ばされた感じ?画像データ配列の変換がおかしくなったようだ。

パターン1


パターン2



BufferedImage の setRGB() の説明を読むと、
デフォルトモデルがイメージ ColorModel と一致しない場合は色変換が行われます。
と書かれていたのでこちらを使うようにしてみた。


パターン 3BufferedImage#setRGB() を使用

public static BufferedImage grayScale3(BufferedImage image) throws Exception {
  int w = image.getWidth(), h = image.getHeight();
  BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
  output.setRGB(0, 0, w, h, image.getRGB(0, 0, w, h, null, 0, w), 0, w);
  return output;
} 

パターン1にくらべかなり黒っぽくなった。

パターン3


今回は、パターン1がよさそうだ。

実は、実際嵌った時の画像では、パターン1の場合、元画像の白の部分がグレーがかってしまいパターン3で出力したほうがはっきりしたものになった。
元画像によって変えるのも大変だが・・・

Graphics2D の場合、パラメータを色々指定できるのでそのあたりをいじってみても変わるかもしれない。

2013年8月7日水曜日

Googlebot のクロール時の jsessionid

とあるシステムでアクセスログを見ていたら、Googlebot が jsessionid 付の URL でアクセスしていた・・・

初回アクセス時や、Cookie 未対応の端末の場合、jsessionid を付ける設定になっている。
セキュリティー上よろしくないのだが、ガラケー対応のサイトのため、
アプリケーションサーバ Tomcat の設定で止めることができない。

ログイン必須のページとそうでないページがあり、Googlebot がログインするわけでもないので、
無視することもできるが、SEO 的によろしくない気がする。何より気持ち悪い・・・
というわけで対応することに。

ググってみると、URL Rewrite Filter なるものがあるらしい。
mod_rewrite のフィルターバージョンとのこと。
mod_rewrite にリンク等を書き換える機能があったっけと思いつつみてみると、
<%= response.encodeURL("/world.jsp?country=usa&amp;city=nyc") %>

<c:url value="/world.jsp?country=${country}&amp;city=${city}" />

のように指定した URL が正規表現で指定した値に置換されるようだ。

SAStruts や mobylet を使用しているので、c:url はほとんど使っていないが、
フレームワークの内部的には、response.encodeURL を使っているだろうと思われるので問題はなさそう。

ただ、jsessionid を付加しておいて置換で消すというのは冗長な気がする・・・
ソースを読んだわけではないので実装がどうなっているかは分からないが・・・
最初から付加しなければいいはず。

Tomcat の HttpServletResponse.encodeURL は、
HttpServletRequest.isRequestedSessionIdFromCookie を見てた気がする、ということで、
Bot の UserAgent だった場合、HttpServletRequestWrapper を作成し、
isRequestedSessionIdFromCookie で常に true を返すことに。

public class BotFilter implements Filter {
  private static class BotRequesteWrapper extends HttpServletRequestWrapper {
    public CrawlerRequestWrapper(HttpServletRequest request) {
      super(request);
    }

    @Override
    public boolean isRequestedSessionIdFromCookie() {
      return true;
    }

    @Override
    public boolean isRequestedSessionIdFromURL() {
      return false;
    }
  }


  private static final Pattern PAT_BOT =
    Pattern.compile("Googlebot|Y!J|YahooSeeker|msnbot|bingbot");


  @Override
  public void init(FilterConfig filterConfig) throws ServletException {}

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest)request);
    chain.doFilter(PAT_BOT.matcher(req.getHeader("User-Agent")).find() ? new BotRequestWrapper(req) : request, response);
  }

  @Override
  public void destroy() {}
}

Google, Yahoo, MSN, Bing 等の場合、isRequestedSessionIdFromCookie で常に true、
念のために、isRequestedSessionIdFromURL は 常に false を返す。
そして、フィルターを設定。
<filter>
  <filter-name>botFilter</filter-name>
  <filter-class>com.example.BotFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>botFilter</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>INCLUDE</dispatcher>
  <dispatcher>ERROR</dispatcher>
</filter-mapping> 
UserAgent を Bot のものにして、Cookie を削除後アクセスすると、ものの見事に
jsessionid が付加されている・・・

何故・・・と言う事で追ってみると、どうも ServletResponse 内に ServletRequest を保持しており、
 HttpServletRequestWrapper とは別物のよう・・・

仕方がないので、HttpServletResponseWrapper を作成し、直接 encodeURL を変更することに。
public class CrawlerFilter implements Filter {
  private static class BotResponseWrapper extends HttpServletResponseWrapper {
    public BotResponseWrapper(HttpServletResponse response) {
      super(response);
    }


    @Override
    public String encodeRedirectURL(String url) {
      return url;
    }

    @Override
    public String encodeURL(String url) {
      return url;
    }
  }


  private static final Pattern PAT_BOT =
    Pattern.compile("Googlebot|Y!J|YahooSeeker|msnbot|bingbot");


  @Override
  public void init(FilterConfig filterConfig) throws ServletException {}

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    chain.doFilter(request,
      PAT_BOT.matcher(((HttpServletRequest)request).getHeader("User-Agent")).matches() ?
        new BotResponseWrapper((HttpServletResponse)response) : response);
    }

    @Override
    public void destroy() {}
}
encodeUR(), encodeRedirectURL() ともに渡された URL を返すだけにする。

これで、jsessionid が付加されなくなった。

2013年1月17日木曜日

財布経過

財布購入から早一年。
購入した GANZO の財布がどうなったかとういことで。


基本、バッグか上着の内ポケットに入れて持ち歩いているが、角のほうが若干へたってきている。
落としたり、水をこぼしたりしてしまったので、内側外側共に細かい傷がいっぱいついている。内側には若干染みになっている部分も。

エイジング的にはありだが、コードバンの場合最初のきれいな状態を保ちたいという感じもしてしまう・・・

手入れは、GANZO のサイトに、「乳化性タイプ無色 成分はろう・油脂の栄養補給を目的としたクリーム」でアフターケアをと書かれてたので、M.モゥブレィシュークリームという靴用のクリーム購入。

これでたまに磨いている。
外側の折る背の部分が若干カサつく。折れる部分なので仕方ないんだろう。念入りクリームを塗っている。

硬さは 1 年前に比べて大分柔らかくなり、360度開くことも可能になった。
小銭部分はマチがないのであまり多いと取り出し難い。
カードも取り出しやすくなった。伸びてガバガバになっているわけでもなくいい感じ。
 さらに使い込んでいこうと思う。

2012年12月27日木曜日

sbt で設定ファイルを本番用に切り替え

Scala の勉強をかなりさぼっていたわけで・・・
構文も大分忘れている状態なんですが、またちょっとはじめることに。
というか実践。

sbt を使ってちょっとしたバッチを開発時に jar に含める設定ファイル(ログレベル設定等)を、開発、ステージング、本番用に切り替えたくなった。そして嵌った・・・約 2 日・・・

とりあえず設定ファイルの構成は、Java の時にやっていた構成で、
+ src
  + main (開発)
    + scala
    + resources
      + logback.xml
      + ...
  + product (本番)
    + resources
      + logback.xml
  + staging (ステージング)
    + resources
      + logback.xml
の構成で、product/resources, staging/resources には上書きするもののみを置くこととする。

始める sbt のスコープ軸を見ると、タスク毎に設定が出来そう。ということで、最初のアプローチは、pack-staging, pack-product のタスクを生成して、タスクのスコープで unmanagedResourceDirectory を追加しようとした。が、そこから呼び出す copy-resources は copy-resources in Compile になってしまい挫折・・・これに 1 日以上費やしたわけだが・・・結局わからなかった・・・

で、maven の profile 的なことができないかと検索してみると参考になるサイトがあった。
ここを参考に、
case class Profile(name: String)

object Profile {
 val staging = Profile("staging")
 val product = Profile("product")

 implicit def toOption(profile: Profile): Option[Profile] = Option(profile)
}
として、
  unmanagedResourceDirectories in Compile <<= (sourceDirectory, selectedProfile, unmanagedResourceDirectories in Compile)
    ((dir, env, res) => env.map((dir / _.name / "resources")).toSeq ++ res),
  unmanagedResources in Compile~= (_.groupBy((_: java.io.File).getName).map(_._2.head).toSeq),
リソースディレクトリの設定時に振り分け。さらに、リソースファイル設定時に一意になるようグループ化しているようなしていないような・・・いまいち何をやっているかわからないが完成・・・ 

記号がさっぱりわからん・・・Perl とまでは言わんが・・・演算子でなくメソッドなので色々できるわけだが、C++ みたいに標準的な演算子のみオーバーライドできればいいのに・・・ ライブラリ毎に覚えるのは大変だ・・・ 標準的な意味合いの記号があるのだろうか。

 とりあえず sbt の API ドキュメントより、Setting[T] の <<= メソッドを見てみると trait DefinableSetting[S] に定義があり、
final def <<=(app: Initialize[T]): Setting[T]
なるほど、Initialize[T] を受け取る。 unmanagedResourceDirectories は SettingKey[Seq[File]] なので、右辺の
(sourceDirectory, selectedProfile, unmanagedResourceDirectories in Compile)
    ((dir, env, res) => env.map((dir / _.name / "resources")).toSeq ++ res)
は、Initialize[Seq[File]] を返すのであろう。

で、右辺部分だが、
(sourceDirectory, selectedProfile, unmanagedResourceDirectories in Compile)(なんかの処理)
つまり、
(タプル).apply(なんかの処理)
のように見えるが、タプルに対してそんなことできるのか・・・おそらくは暗黙の型変換・・・
というわけで、ソースコードを implisit で grep すると、Structure.scala で
implicit def t3ToApp3[A,B,C](t3: (Initialize[A], Initialize[B], Initialize[C]) ): Apply3[A,B,C] = new Apply3(t3)
というのを発見。さらに、Apply3 をみると、
def apply[T](z: (A,B,C) => T) = Project.app( k3(t3) )( hf3(z) )
となっている。さらに、object Project の API ドキュメントに、Init[Scope] に app の定義があり
def app[HL <: HList, T](inputs: KList[Initialize, HL])(f: (HL) ⇒ T): Initialize[T]
なので、Initialize[Seq[File]] が返りそう。なんか予想が多いが・・・

次に、なんかの処理部分は、Apply3 の apply の引き数なので、
(A,B,C) => T
最初のタプルの設定の値がそれぞれ渡り、T つまり、Seq[File] を返す関数を渡せばよさそう。
unmanagedResourceDirectories が SettingKey[Seq[File]] なので、res は Seq[File] とおもわれる。
そして、生成したパスと res を連結したものが返ると。

最終的に返った、Initialize[Seq[File]] で unmanagedResourceDirectories in Compile を設定しているようだ。
とりあえず、今回はここまで。
追うのだけでも大変・・・自分でカスタマイズ出来る気がしない・・・Scala は嫌いじゃないけど慣れないと、読むのも困難だな・・・

2012年10月19日金曜日

Java による画像縮小 lanczos 編

JDK を使った縮小の結果納得行かなかったので、GIMP なんかにもある lanczos による縮小を実装してみることに。
元画像は Jpeg なので、RGB の 3 色のみを扱う。

こちらの Ruby バージョンをとりあえず Java にポーティング。
今回縮小のみなので、拡大部分を削除。実行してみると 6 秒前後かかった・・・

もう少し早くならないかなと、さらにコメントのリンク先を読んでみる。

何をやっているかというと、中心を 1 として距離によって重みを付けるということのようだ。
| 0.3 | 0.5 | 0.3 |
|-----+-----+-----|
| 0.5 | 1.0 | 0.5 |
|-----+-----+-----|
| 0.3 | 0.5 | 0.3 |

といった具合に。
重みを計算するのが lanczos 関数。
lanczos2 や lanczos3 の数字は距離で、縮小画像のあるピクセルを計算するために、対応する元画像ピクセルからどれくらいの範囲を参照するかを決めるものっぽい。

となると、縮小率と距離の数字が決まれば、対象のピクセルがどこであろうと、重みは一緒?
というわけで、先に上記のような重みの表を作成しておき、各ピクセル計算時はそれを使用すれば計算量は減るだろう。

中心からのオフセットと重みを保持するクラス

public class Weight {
  public final int offsetX;
  public final int offsetY;
  public final double weight;

  public Weight(int x, int y, double w) {
    offsetX = x;
    offsetY = y;
    weight = w;
  }
}

重みの一覧と合計を保持するクラス

public class WeightFilter {
  public final double weightTotal;
  public final Weight[] weightList;


  public WeightFilter(double sumw, Weight[] weights) {
    weightTotal = sumw;
    weightList = weights;
  }
}

lanczos 関数

public static double lanczos(double x, double n) {
  if (x == 0.0) return 1.0;
  if (Math.abs(x) >= n) return 0.0;
  return Math.sin(Math.PI * x) / Math.PI / x * (Math.sin(Math.PI * x / n) / Math.PI / x * n);
}

縮小率と距離で重み一覧作成

縦・横別でなく斜めも距離を計算してみる
public static WeightFilter getReduceFilter(double scale, int distance) {
  double bdx0 = 0 + 0.5, bdy0 = 0 + 0.5;
  int ss = (int)((bdx0 - distance) / scale), se = (int)((bdx0 + distance) / scale);
  double sumw = 0.0;
  List<Weight> weightList = new ArrayList<>((se - ss) * (se - ss));

  for (int sy = ss; sy <= se; ++sy) {
    for (int sx = ss; sx <= se; ++sx) {
      double xl = (sx + 0.5) * scale - bdx0;
      double yl = (sy + 0.5) * scale - bdy0;
      double w = lanczos(Math.sqrt(xl * xl + yl * yl), distance);
      if (w == 0.0) continue;
      weightList.add(new Weight(sx, sy, w));
      sumw += w;
    }
  }
  return new WeightFilter(sumw, weightList.toArray(new Weight[0]));
}


ユーティリティ関数も作成。

指定座標の RGB 値取得

範囲外は鏡像として取得。
public static int getRGB(int[] image, int x, int y, int w, int h) {
  x = Math.abs(x); y = Math.abs(y);
  if (x >= w) x = w + w - x - 1;
  if (y >= h) y = h + h - y - 1;
  return image[y * w + x];
}

0 - 255 の範囲で色値取得

public static int getColor(double val, double sumw)
{
  if (sumw != 0.0) val /= sumw;
  if (val < 0.0) return 0;
  if (val > 255.0) return 255;
  return (int)val;
}

縮小処理

public static BufferedImage reduce3(BufferedImage image, int n, int dw, int dh)
  int sw = image.getWidth(), sh = image.getHeight();

  double scale = (double)dw / sw;
  BufferedImage thumb = new BufferedImage(dw, dh, image.getType());
  WeightFilter filter = getFilter(scale, n);
  double sumw = filter.weightTotal;
  int[] colors = image.getRGB(0, 0, sw, sh, null, 0, sw);

  for (int dy = 0; dy < dh; ++dy) {
    for (int dx = 0; dx < dw; ++dx) {
      double dr = 0.0, db = 0.0, dg = 0.0;
      int bsx = (int)(dx / scale), bsy = (int)(dy / scale);
      for (Weight weight : filter.weightList) {
        int rgb = getRGB(colors, bsx + weight.offsetX, bsy + weight.offsetY, sw, sh);
        dr += ((rgb >> 16) & 0xFF) * weight.weight;
        dg += ((rgb >> 8) & 0xFF) * weight.weight;
        db += (rgb & 0xFF) * weight.weight;
      }
      thumb.setRGB(dx, dy, (getColor(dr, sumw) << 16) | (getColor(dg, sumw) << 8) | getColor(db, sumw));
    }
  }
  return thumb;
}

呼び出し

lanczos2 と lanczos3 で縮小。
long start3_1 = System.currentTimeMillis();
BufferedImage reduce3_1 = reduce3(base, 2, 120, 90);
System.out.println("reduce3_1: " + (System.currentTimeMillis() - start3_1));
outputImage("reduce3_1.jpg", reduce3_1);

long start3_2 = System.currentTimeMillis();
BufferedImage reduce3_2 = reduce3(base, 3, 120, 90);
System.out.println("reduce3_2: " + (System.currentTimeMillis() - start3_2));
outputImage("reduce3_2.jpg", reduce3_2);

結果

lanczos2: 126.3ms
lanczos3: 233.0ms

元画像と左から、パターン1 3段階、パターン2 3段階、lanczos2、lanczos3




大分早くなった。JDK に比べると遅いが画質はいい。
一度作成してた後は、キャッシュするようにすればいいので、満足。

Java による画像縮小 JDK 編

忙しい日々が続いて、色々嵌っているが書けていない・・・
また同じ事で嵌まることになるだろう・・・

半年以上前だが、Java で画像を縮小してサムネイルを作成する際に、なるべく画質を落としたくなかったので色々試したのでその記録を。
結局ブログ書くために再調査したわけだけ・・・

元となる画像(1024 x 768)は、
これから 120 x 90 のサムネイルを作成することとする。


共通処理として、

画像読み込み

public static BufferedImage loadImage(String filename) {
  try (FileInputStream in = new FileInputStream(filename)) {
    return ImageIO.read(in);
  }
}

画像出力

// 品質 95 Jpeg
public static void outputImage(String filename, BufferedImage image) throws IOException {
  JPEGImageWriteParam param = new JPEGImageWriteParam(Locale.getDefault());
  param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
  param.setCompressionQuality(0.95f);

  ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
  writer.setOutput(ImageIO.createImageOutputStream(new File(filename)));
  writer.write(null, new IIOImage(image, null, null), param);
  writer.dispose();
}
の 2 つを用意。

縮小処理は、2 パターン。

パターン1 BufferedImage#getScaledInstance() を使用

public static BufferedImage reduce1(BufferedImage image, int dw, int dh) {
  BufferedImage thumb = new BufferedImage(dw, dh, image.getType());
  thumb.getGraphics().drawImage(image.getScaledInstance(dw, dh, Image.SCALE_AREA_AVERAGING), 0, 0, dw, dh, null);
  return thumb;
}

パターン 2 Graphics2D#drawImage() を使用

public static BufferedImage reduce2(BufferedImage image, int dw, int dh) {
  BufferedImage thumb = new BufferedImage(dw, dh, image.getType());
  Graphics2D g2d = thumb.createGraphics();
  g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
  g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
  g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
  g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
  g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
  g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
  g2d.drawImage(image, 0, 0, dw, dh, null);
  return thumb;
}

パターン1、パターン2 をそれぞれ呼び出し

try {
  BufferedImage base = loadImage("base.jpg");
  int dw = 120, dh= 90;

  long start1_1 = System.currentTimeMillis();
  BufferedImage reduce1_1 = reduce1(base, dw, dh);
  System.out.println("reduce1_1: " + (System.currentTimeMillis() - start1_1));
  outputImage("reduce1_1.jpg", reduce1_1);

  long start2_1 = System.currentTimeMillis();
  BufferedImage reduce2_1 = reduce2(base, dw, dh);
  System.out.println("reduce2_1: " + (System.currentTimeMillis() - start2_1));
  outputImage("reduce2_1.jpg", reduce2_1);
} catch (Exception e) { e.printStackTrace(); } 

パターン1はぼやけた感じ、パターン2はざらついた感じ。
連続で 4 回実行し、1 回目を除いた 3 回の平均時間を計測。
速度はパターン2が早いが、今回は作成した画像をキャッシュするので速度より画質。

パターン1:  80.3ms


パターン2: 0.7ms


段階的に縮小

近傍縮小法だと、大きな画像から一気に縮小すると色情報の欠落が大きくなるので、段階的に縮小してみる。
  • 1024x768 -> 640x480 -> 120x90 の 2 段階
  • 1024x768 -> 800x600 -> 480x360 -> 120x90 の 3 段階
の 2 つそれぞれのパターンで実行。
int dw2_1 = 640, dh2_1 = 480;
int dw3_1 = 800, dh3_1 = 600, dw3_2 = 480, dh3_2 = 360;

long start1_2 = System.currentTimeMillis();
outputImage("reduce1_2.jpg", reduce1(reduce1(base, dw2_1, dh2_1), dw, dh));
System.out.println("reduce1_2: " + (System.currentTimeMillis() - start1_2));

long start2_2 = System.currentTimeMillis();
outputImage("reduce2_2.jpg", reduce2(reduce2(base, dw2_1, dh2_1), dw, dh));
System.out.println("reduce2_2: " + (System.currentTimeMillis() - start2_2));

long start1_3 = System.currentTimeMillis();
outputImage("reduce1_3.jpg", reduce1(reduce1(reduce1(base, dw3_1, dh3_1), dw3_2, dh3_2), dw, dh));
System.out.println("reduce1_3: " + (System.currentTimeMillis() - start1_3));

long start2_3 = System.currentTimeMillis();
outputImage("reduce2_3.jpg", reduce2(reduce2(reduce2(base, dw3_1, dh3_1), dw3_2, dh3_2), dw, dh));
System.out.println("reduce2_3: " + (System.currentTimeMillis() - start2_3));
パターン1 2段階: 141.7ms


パターン1 3段階: 215ms


パターン2 2段階: 7.3ms


パターン2 3段階: 16.3ms


多少マシになったが、ちょっと納得いかないレベル・・・
続く

2012年4月22日日曜日

Apache + Tomcat のレスポンス

ここのところ忙しく、全然更新していなかったが、実際には色々嵌りまくりな日々・・・

その中でも最近一番大きかったのが、Apache + Tomcat の構成でレスポンスがおかしい現象が発生。
しばらく使っていると、別のリクエストのレスポンスが返ってくるようになる。
別のユーザのユーザ情報が見えたり・・・セッションがおかしくなったり・・・

Apache または、Tomcat のいずれかを再起動すると普通に戻るがしばらくするとまた発生し始める
プログラムのせいではないことを祈り調査。

接続は mod_proxy_ajpを使用して mod_proxy_balancer で分散していた。
分散していないテスト環境でも発生していたため、mod_proxy_balancer は調査から除外。
mod_proxy_ajp を mod_proxy_http に変更したところ発生しなくなった。
過去に何度も Apache + Tomcat, mod_proxy_ajp 構成をやったことはあるが初めての現象。

次に、Apache 2.4 を使っていたため、2.2 にダウングレード。
mod_proxy_ajp で接続して問題は発生しなかった。

つまり、Apache 2.4 の mod_proxy_ajp が問題だったと思われる。
Apache 2.4 は今回初めて使用したが、まだ早かったんだろうか・・・

2012年2月12日日曜日

Pacemaker で仮想 IP の切り替え

Pacemaker でまず仮想 IP の切り替えを行う時に嵌まったこと。

OS は CentOS 5.7 でやってみる。インストールは、LINUX-HA JAPAN より、ローカルリポジトリ + yum を使用。

クラスタ制御部には、Corosync を使用することにする。
/etc/corosync/corosync.conf を作成。
# Please read the corosync.conf.5 manual page
compatibility: whitetank

aisexec {
        user: root
        group: root
}

service {
    name: pacemaker
    ver: 0
    use_mgmtd: yes
}

totem {
    version: 2
    secauth: off
    threads: 0
    rrp_mode: active
    clear_node_high_bit: yes
    token: 4000
    consensus: 10000
    rrp_problem_count_timeout: 3000
    interface {
        ringnumber: 0
        bindnetaddr: 192.168.234.0
        mcastaddr: 226.94.1.1
        mcastport: 5405
        ttl: 1
    }
}

logging {
    fileline: on
    to_stderr: no
    to_logfile: yes
    to_syslog: no
    logfile: /var/log/cluster/corosync.log
    debug: off
    timestamp: on
}
bindnetaddr にはインターフェースの IP でなくネットワークアドレスを指定する。
ログをファイルに出力するように指定。

/etc/init.d/corosync start で起動して、crm_mon を実行して確認する。
Attempting connection to the cluster.........................
帰ってこない・・・

一般ユーザで実行したからっぽい。グループに、haclient を追加したら帰ってきた。
============
Last updated: Sun Feb 12 00:54:09 2012
Stack: openais
Current DC: localhost.localdomain - partition WITHOUT quorum
Version: 1.0.11-1554a83db0d3c3e546cfd3aaff6af1184f79ee87
1 Nodes configured, 2 expected votes
0 Resources configured.
============

Online: [ localhost.localdomain ]

次に、 crm configure help でコマンドを確認しようとするも、
cibadmin not available, check your installation
となる、sudo でやってもダメだった。仕方ないので root になって行う。
# crm configure property no-quorum-policy="ignore" stonith-enabled="false"
# crm configure rsc_defaults resource-stickiness="INFINITY" migration-threshold="1"
# crm configure primitive vip ocf:heartbeat:IPaddr2 \
params ip="192.168.234.51" nic="eth0" cidr_netmask="24" op monitor interval="10s"
eth0 に 192.168.234.51 の仮想 IP を作成する設定。

ifconfig で確認するも設定したアドレスが見当たらない。
eth0 にはそもそも実インターフェースの IP が割当たっているからだ。
ip addr show eth0 で確認できる。
inet 192.168.234.51/24 brd 192.168.234.255 scope global secondary eth0

最初、CentOS 6 を使用して普通に yum でインストールして開始したわけだが、こっちのほうが嵌まった。
Pacemaker が 1.1 になっており、クラスタ部に heartbeat が使えなかったり、corosync.conf の service の ver を 1 にしないとだったり、vip 指定時に nic に eth0:1 などを指定すると警告が出たりとかなり面倒だった。

結局、CentOS5.7 を使うことになり、落としたわけだが・・・

2012年1月11日水曜日

SAStruts + Maven

久々、Java での Web アプリケーションを作成をすることに。
最近のフレームワークが分からないため、今更 SAStruts を選択。最近は何がメジャーなんだろうか・・・

とりあえず開発環境を最新版でそろえていく。
  • JDK 1.7.0_02
  • Maven 3.0.3
  • Eclipse 3.7 64bit(Pleiades のプラグイン + Maven プラグイン)
  • Tomcat 7.0.23 -> 6.0.35
Maven で SAStruts のプロジェクトを作成。
# mvn archetype:generate -DarchetypeRepository=https://www.seasar.org/maven/maven2/ \
  -DarchetypeGroupId=org.seasar.sastruts \
  -DarchetypeArtifactId=sa-struts-archetype -DarchetypeVersion=1.0.4-sp9.0 \
  -DgroupId=com.example.sample.sas -DartifactId=sas -Dversion=1.0.0 
Eclipse 用プロジェクト作成。
# cd sas
# mvn eclipse:eclipse -Dwtpversion=1.5
Eclise で「インポート」->「一般」->「既存プロジェクトをワークスペースへ」でインポート。が、ここで問題発生。
プロジェクトを右クリックしても Maven のメニューが出てこない・・・ Maven プラグインは入れたはずなのに。 昔はこれでいけてた気がしたが、バージョンが上がって変わったのだろうか。

色々検索したり、Maven のバージョン変えたりプラグインを再インストールしたりしてみたがうまくいかない。で、かなりして Eclipse のインポートメニューに Maven の項目を発見。
 mvn archietype:generate でプロジェクト作成後、mvn eclipse:eclipse は行わずに、Eclipse の「インポート」->「Maven」->「Existing Maven Project」でインポート。途中、「maven-antrun-plugin:1.3:run (1 errors)」 とエラーが出るがかまわず完了。これで、プロジェクトの右クリックメニューに Maven の項目が出た。

取り込み後、pom.xml に警告とエラーが出ているので潰していく。
まず、<project> タグで警告がでるため、クイック・フィックスで修正。

次に、maven-antrun-plugin の「Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-antrun-plugin:1.3:run (execution: delete-lib-dir, phase: initialize)」もクイック・フィックスの「Permanently mark goal run in pom.xml as ignored in Eclipse build」で修正する。

最後の、maven-dependency-plugin の「maven-dependency-plugin (goals "copyff-dependencies", "unpack") is not supported by m2e.」は、上記修正で追加された、pluginManagement/plugins/plugin/configuration/lifecycleMappingMetadata/pluginExecutions に設定を追加する。
<pluginExecution>
  <pluginExecutionFilter>
    <groupId>
      org.apache.maven.plugins
    </groupId>
    <artifactId>
      maven-dependency-plugin
    </artifactId>
    <versionRange>[1.3,)</versionRange>
    <goals>
      <goal>copy-dependencies</goal>
    </goals>
  </pluginExecutionFilter>
  <action>
    <ignore></ignore>
  </action>
</pluginExecution>
ついでに、build/plugins/plugin/artifactId が maven-compiler-plugin の source と target を 1.7 に変更。

プロジェクトを右クリックして 「Maven」 -> 「プロジェクト構成の更新」を実行して反映すればエラーと警告が消えた。

次に、WTP の設定。
プロジェクトを右クリックしてプロパティを開き、プロジェクト・ファセットを選択。
Convert to faceted from... をクリック。動的 Web モジュールにチェックを入れて、バージョンを確認すると、3.0 になっている。SAStruts は 3.0 で動くのだろうか・・・調べてみるとどうもダメっぽいので、2.5 に変更。よって Tomcat も 6 に落とす。
プロジェクト・ファセットの設定がなぜだか、1 回目だと失敗する。2 回やると通ったのでそのまま続けることにする・・・
次に、プロパティダイアログの Deployment Assembly を選択。/WebContent の行を削除。追加をクリックして、 「フォルダー」より src/main/webapp を追加。/WebConent のディレクトリは物理的に削除して大丈夫だと思われる。さらに、追加をクリックして、「Java Build Path Entries」より Maven 依存関係を追加。

src/main/webapp/WEB-INF/web.xml のバージョンが 2.4 になっているので 2.5 に変更。
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  id="WebApp_ID" version="2.5">
dependencies/dependency の geronimo-jsp_2.0_spec を geronimo-jsp_2.1_spec に、バージョンを 1.0 から 1.0.1 に変更。
同じく、geronimo-servlet_2.4_spec を geronimo-servlet_2.5_spec に、バージョンを 1.0 から 1.2 に変更。

プロジェクトを右クリックして、「実行」->「サーバで実行」でサーバを起動すると特にエラーもなく無事起動。
だが、ブラウザで http://localhost:8080/sas/ にアクセスすると、「org.apache.jasper.JasperException: /WEB-INF/view/index.jsp(1,1) /WEB-INF/view/common/common.jsp(1,61) JARファイル "file:/ワークスペースのパス/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/sas/WEB-INF/lib/standard-1.1.2.jar" からTLD "META-INF/c.tld" を読み込めません: org.apache.jasper.JasperException: TagLibraryValidatorクラスのロード又はインスタンス化に失敗しました: org.apache.taglibs.standard.tlv.JstlCoreTLV 」と 500 エラーになる。 

これも結構嵌まったが、どうも pom.xml の provided の jar を読み込んでいる模様。
WTP 実行時に、pom.xml の povided を認識しないらしい。

検索してみると m2e-wtp なるものを発見。インストールしてみる。
「ヘルプ」->「Eclipse マーケットプレース」検索してインストール。
プロジェクトを右クリックして 「Maven」 -> 「プロジェクト構成の更新」を実行後、起動すると無事「Hello!」が表示された。

プラグインをインストールしたことでかなり飛ばせる手順がありそうだが、次回もこの手順でやれば同じ結果になるだろう・・・

2012年1月3日火曜日

財布購入

革の財布が欲しかったので、財布を買うなら正月かなということで商品券もあったことだし百貨店に行って来た。

近所の、伊勢丹、日本橋の三越本店へ。さらに、銀座の松坂屋と三越に行ってみた。 長財布で小銭入れ付かラウンドファスナーのタイプが欲しくて、気になったものはいくつがあったが、これ!というものがなかったので転々と。
最後の銀座三越で気に入ったものがあったが、予算より若干オーバー。しかも、コードバンとブライドルレザーの 2 種類あって悩むことに。店員さんの説明を聞いたりして長々と考えたが、悩んでる時点で買うことはほぼ確定していたんだろう。結局コードバンを買ってしまった。

GANZO のコードバンの長財布、小銭入れ付。影が写り込んでるが・・・
色はダークブラウン。38,850円だった。

 コードバンなので硬いのは当然だけが、カードを入れるのが結構大変。カード入れ全てにカードを入れるとパンパン。完全開くにも硬いので小銭や札を出すだけでも結構厄介。 ブライドルレザーのほうが少し軟らかく、完全に開いた上にさらに反対側に開いたので扱いやすいだろうなといったところでも迷ったわけだが。
まぁなじむだろうと期待してコードバンにしてみた。使い込んでいかないと。

2011年12月21日水曜日

is_writable の実装探しの旅

pgpoolAdmin のインストール時のエラーの続き。

selinux が原因だったので、 is_writable の実装探しの旅に出ても結局分からなかったと思われる。
が、気になったので、実装探しの旅に出てみることにした。

emacs + cscope で読むことにする。
php のソースコードを展開して、cscope のファイルを作成。
$ cd php-5.3.8
$ find ./ -name "*.h" -or -name "*.c" > cscope.files
$ cscope -b -q -k
emacs で展開したディレクトリあたりを開いて、 M-x cscope-find-global-definition is_writable を実行。
まぁそう簡単には見つからない。M-x cscope-find-this-symbol is_writable でいくつか候補が。
その中でそれっぽいのが、
*** ext/standard/php_filestat.h:
<global>[39]                   PHP_FUNCTION(is_writable);
...
*** ext/standard/basic_functions.c:
ZEND_END_ARG_INFO[3194]        PHP_FE(is_writable, arginfo_is_writable)
宣言と定義っぽいのが見つかったが、マクロのようだ・・・

宣言っぽいほうを追っていくと
#define ZEND_FN(name) zif_##name
とか出てくる。zif_iswritable とか作られているのだろうか。

次に定義っぽい方を追っていくと、
const zend_function_entry basic_functions[] = { /* {{{ */
  [snip]
    PHP_FE(is_writable,       arginfo_is_writable)
となっている。
関数ポインタを持った構造体の配列?これを追うのは大変そうだ・・・そもそもの仕組み知らないし・・・呼び出し元が探せない・・・
という訳で、GDB でステップ実行してみることに。

http://d.hatena.ne.jp/anatoo/20111117/1321463886
を参考にまず、まずコンパイル。
$ ./configure --disable-all --enable-debug
$ make
でテスト用のスクリプト(~/test_is_writable.php)を作成。(前回通らないと思ってたが Web 経由だとユーザが異なっていてパーミッションの問題だったかも・・・パーミッション確認後、再度実行したらこのスクリプトは true となった)
<?php
echo is_writable('/usr/local/etc/pgpool.conf');
次に、GDB で実行。
$ gdb sapi/cli/php
(gdb) break zif_is_writable
Breakpoint 1 at 0x509b30: file /usr/local/src/php-5.3.8/ext/standard/filestat.c, line 1092.
(gdb) run ~/test_is_writable.php
後は、s と n で辿っていく。
ext/standard/filestat.c:782 の php_stat が実装のようだ。
さらに入っていくと、
eck_open_basedir_ex (path=0xb03350 "/usr/local/etc/pgpool.conf", warn=1)
    at /usr/local/src/php-5.3.8/main/fopen_wrappers.c:297

 if (PG(open_basedir) && *PG(open_basedir)) {
あたりだろうか。
PG の定義は、
# define PG(v) (core_globals.v)
extern ZEND_API struct _php_core_globals core_globals;
_php_core_globals の open_basedir が設定されているかどうかを見ているっぽい。
が、open_basedir が分からない。
ググッて見ると、php.ini にそういう項目があるようだ。
このあたりをいじればいけるかと思ったがそうでもなかった。

テストスクリプトだと true となるのでこのあたりで断念。
結局追えなかった・・・

2011年12月12日月曜日

pgpoolAdmin のインストール時のエラー

今回は、pgpoolAdmin を検証するためにインストールしていた時に嵌まったことを。

pgpoolAdmin を展開後、http://www.example.com/pgpoolAdmin/install/index.html にアクセスしてインストール作業を続けるわけだが、/usr/local/etc 以下の pgpool.conf や pcp.conf が "Write access denied" となっていて先に進めない。

ファイルのパーミッションや所有者を確認してみるが特に問題はなさそうだ。
PHP で作られているようなので中身を見てみることにした。

install/checkParameter.php 60
if (!is_writable($pgpool2_config_file)) {
  $msgPgpoolConfigFile = 'Write access denied';
  $error = TRUE;
}
となっている。

実行ユーザを調べるために
echo `id`;
を埋め込んでみるも、想定通りのユーザで動いてそうだ。

スクリプトからパーミッションを取得するために
var_dump(stat($pgpool2_config_file));
を埋め込んでみるも、
["mode"]=> int(33188)
となっており、8 進数で 100644 となり、これも問題なさそう。

is_writable のみを記述したテストプログラムを作成してみるが、この時点で false が帰る。後日、動かしてみたら true だった。おそらくこれはパーミッションだったかな・・・
これは困った。is_writable の実装を探す旅にでるしかないのかと思ったが、ふと英語が出来ないなりに「is_writable always false」でググったら一番上に出てきた。
selinux... すっかり忘れていた・・・コネクション周りなら思い出せたかもしれなかったが。

# setenforce 0
ついでに、/etc/selinux/config の SELINUX を disabled に変更。
で無事にインストールを続けることができた。

続く...

2011年12月7日水曜日

複数 FLV ファイルから DVD 作成

今回は開発以外で嵌まったことを。
知人に複数 FLV から DVD を作成したいと言われたので調査することに。これが思いのほか嵌まった・・・

そもそも、動画を変換、編集することがかなり久しぶりだった。昔は、DVD-RAM で TV 番組を録画して CM カットして DVD-R に保存なんてこともやっていたが、何年も前であまり覚えていない。
前は、DVD ドライブを買うと、動画編集ソフトやオーサリングソフトがバンドルされていたが、現在手元に何もない・・・なのでフリーソフトでやるしかない。

まず、昔の手順的なものを思い出すことに。たしか、
  1. DVD-RAM(vob たしか中身は mpeg2)に録画
  2. GOP 単位でざっくり CM カット
  3. AVI(huffyuv 可逆圧縮)変換
  4. フレーム単位で細かくカット
  5. mpeg2 に変換
  6. オーサリング
  7. DVD 焼き
みたいなことをやってたはず。途中どっかの手順で音を分離して最後に結合してたような記憶もあるが。
とりあえず、今回は編集が入らないので、
  1. FLV
  2. AVI(huffyuv)に変換
  3. オーサリング(mpeg2 に変換含む)
  4. DVD 焼き
でいけそうだ。

まず、FLV の変換だが、Any Video Converter というソフトでいけそうだ。これで AVI に変換。オーサリングは、Windows7 についてるWindows DVD メーカーを使用した。Windows DVD メーカーは ISO ファイルは作れずに一気に DVD に焼くようだ。
簡単にできたと思い、中身を確認してみると、出た!音ずれだ・・・
そういえば昔も嵌まった・・・だから音を分離して結合してなんて記憶が残ってたわけだ。ちゃんと確認してみると、AVI の時点でずれている・・・先に確認すれば 1 枚無駄にならなかったのに・・・

とりあえず Any Video Converter で変換時に音を分離することにする。で、VirtualDub で結合して AVI の時点で音があってる状態にしてみることに。
ここでさらに問題が。Any Video Converter で作成した AVI(huffyuv) が VirtualDub で開けない。VirtualDub の圧縮の選択肢の中に huffyuv があるというのに。つまりコーデックは入っているということだが。調べてみたら動画再生、作成等の API が 2 種類あるらしい。
  • Video for Windows(コーデックを使用)
  • DirectShow(フィルターを使用)
知っているのは前者のみだったわけで、そういえば ffdshow なるものも入れた気がする。Any Video Converter は両方(?)対応、VirtualDub は前者対応のようだ。
VirtualDub のプラグインで DirectShow を使用するものがあったので入れてみるがうまく認識しなかった。直接 FLV を読み込むプラグインもあるようだったがこれも読み込んでくれない。そもそも、64bit 版の VirtualDub を使っているがプラグインは 32bit 版な気がする。ソース付のもあったのでコンパイルしなおす等もしてみたが、あっているかも分からずやはり認識せず。
仕方ないので、ほかに可逆圧縮はないものかと探したところ、ffv1 とういものが共通で扱えたのでこれでやることに。
で、Any Video Converter で ffv1 を出力後、VirtualDub で読み込み、分離した音を結合した AVI を作成。作成された AVI を見てみると、音がずれている・・・
さらに確認してみると、そもそも、分離した、ffv1 の動画と wav の音の時間がずれている・・・あうわけがない・・・

VirtualDub でどうにかならないかと「オプション」->「音声に同期させる」や「遅れる場合はフレームを落とす」を選択してみたがダメだった。 「音声」->「インターリーブ」あたりを触ってもダメ。
前はどうやっていたか思い出せないが、有料のソフトがいい塩梅でやってくれてたんだろうか。とりあえずさらに調べてみることに。
すると、「映像」->「フレームレート」の「ソースレート調整」->「映像と音声の再生時間が一致するように変更する」という設定があり、これでいけた。

これで映像と音があった AVI ができたので、次は、mpeg2 変換とオーサリングだ。前回みたく失敗するたびに DVD-R が消費されると辛いので、VIDEO_TS 形式なり ISO イメージなりで出力できるアプリケーションを探すことに。mpeg2 に変換できるもの自体が少ないため、選択肢はあまりなく、その中では、メニュー等も作れる DVD forger がよさそうだったのでこれにすることに。

で、またまた問題が。DVD forger は ffv1 形式が読み込めない。かなり振り出しに戻った気分だ。だがここまできてやめるわけにもいかない。すでにかなりの時間を使っている。
Any Video Converter は他の可逆圧縮を選べない。となると、VirtualDub で音の結合をする時点で別形式に変換することに。
可逆圧縮もさほど選択肢がないわけだが、Ut Video Codec YUV が使えたのでこれにする。正直よくわかってないので、ここでも VfW と DirectShow が入り混じっているのかもしれない。

やっと、DVD forger でオーサリングすることに。とりあえずメニューは作らず。DVD forger には音ずれの設定等設定項目があまりないので mpeg2 に変換時に音がずれないでくれと願いつつ、変換 & オーサリングを実行。
確認しみると、おぉ音があってる!後は焼くだけかと、さらっと見てたら動画が途中で終わっている・・・

実は、FLV ファイルは 4 つあって、その 1 つが再生された時点で終わっていた。
ファイルは個々に変換しているので、Ut Video Codec 形式の AVI も 4 つある。それを、DVD forger に追加すると、タイトルが 4 つできる。メニューを作成して、4 つボタンをつけてみると、ボタンクリックからは再生できる。が 1 タイトル終了後メニューに戻ってしまう。次のタイトルが再生できない。見れないことはないのでいいのかも知れないが、美しくない。俺的にアウトだ。
ということで、Ifo Editor なるものを入れて、ifo ファイルを編集することに。
VIDEO_TS.IFO の「VMGM_PGCI_UT」->「LU_1 PGC-Menu_1」の Pre Command や VTS_01_0.IFO の 「VTS_PGCITI」->「VTS_PGC_1」の Pre Command や Post Command をいじってみることに。
(Jump_TT)Jump to Title や(CallSS_VMGM)Call VideoManager PGC など適当にさわってみるが再生されない。メニューにすら飛ばなくなる。PgcEdit のほうがいいという情報も見かけたので入れてみるが変わらない・・・
まったく知識なくいじっているので仕方がないといえば仕方がないが。かと言って IFO の構造を学ぶのも大変そうだ・・・が、もはや意地でやってるので、さらに調べることに。
  • http://www.geocities.jp/montty_jp/backup/index.htm
  • http://dvd.sourceforge.net/dvdinfo/vmi-jmp.html
が、詳しくて参考になった。見てるとどうも、タイトルからタイトルつまり、VTS から VTS には飛べない感じがする。できた VOB ファイルをみると、VTS_01_0.VOB, VTS_02_0.VOB となっていて、VTS_0* の時点で違いがある。これはもしや、VTS_01_0, VTS_01_1 になればいいのかと、DVD forger の設定を見直してみることに。

「Edit videos, audio tracks and subtitles...」から入ったダイアログに「Merge」なるボタンがあるのに気付く。だが押せない。動画を複数選択しても Disable のまま・・・なんのためのボタンだと思いつつ。View のラジオボタンを適当に押していると、「Outline(for re-orderring, merging and deleting titles)」を選択したときに、Enable になった。なんて使いづらいインターフェースだ。View なんて書くと見た目だけ変わるのかと思うじゃね~かと思ったが、よくみりと、ちゃんと書かれている・・・「Outline(for re-orderring, merging and deleting titles)」
英語は苦手だ・・・

とりあえず、4 つ全ての動画を選択して、Merge をクリック。
メニューのボタンが 1 つになったが問題なし。チャプタは?とも思ったが拘りだしてはキリがないし、俺的に拘るところでもないのでスルーすることに。

確認してみるとちゃんと再生された。音が短くそれにあわせた分一番最後が若干さくっと終わる感じはあるが、ご愛嬌ということで。
後は、DeepBurner で DVD に焼いて終了。焼くのはすんなりいった。

久しぶりに動画の編集をやったがやはり大変。昔に比べてマシンスペックはあがっているので、エンコード時間は短いが、それでも数十分、数時間の待ちがあるので、待ったあとダメだった場合が辛い。
まぁ昔は 30 分の動画をエンコードするのに一晩中動かしても終わってなかったりしてたわけだが。

ちなみに著作権が絡む動画は注意が必要。個人的にダウンロードして複製する分には問題なさそうだが、YouTube などはダウンロードの時点で規約違反となる。違法でなければやっていいというわけでもない。モラルの問題もある。
了承を得られている等、著作権的に問題がない動画ならいいが。
本の自炊屋が消えていったりしているので、著作権が絡む動画を今回のように人に頼まれて変換して渡すとなるとアウトな気がする。

長々と書いてて読みにくいことに気付いた。キャプチャ等を貼るべきかと思うがそれもメンドイ。無理やり文字を大きくしたりでなんとかならんかとやってみたが・・・

2011年11月30日水曜日

三角関数と微分積分

前に読んだ OpenCL のサンプルでフーリエ変換が出てきたのでもう少し詳しく知ろうと、
Excelで学ぶフーリエ変換−Excel 2010対応版−
を最近読み始めたのだが、基本知識がなさすぎて最初の時点でつまづいた。
当然、全てが理解できるとは思っていないが、フリーエ変換のの字も出る前の基本知識の説明の時点でつまづいたら先に進みようがないため調べることに。

でてきた式が

ふむふむ、なるほど、なんで?って感じ。

三角関数は、
  • しんこうこうしん
  • ここマイナスしんしん
  • しんたすしんはにしんこう
  • こうたすこうはにこしん
なんて覚えた記憶がある。でどうやって使うの?
公式の導き方も知ってたような記憶があったが、知ってたかもという記憶だけ・・・

まずは三角関数からかと、ググッて勉強。先は長い。Wikipedia に色々公式が載っていた。
まずは、ピタゴラスの定理より

この時点で悩むことになるが・・・
半径 1 の単円で角度 X の場合、x = cosX, y = sinX となり、直角三角形の斜辺の 2 乗は各辺の 2 乗の和になることから上記、式になるようだ。

次は、加法定理。
しんこうこうしん、ここマイナスしんしんは加法定理のようだ。
左辺を覚えてないと意味がなかった。
加法定理の導き方は Wikipedia に載っていた。
A(cos(-α), sin(-α)) B(cosβ, sinβ) の線分の長さから求めるものしか分からなかったが。
線分 AB の長さの 2 乗は、

途中が省かれてて、最初見たときは?だったが、
上記ピタゴラスの定理により、2 に変換されている。
また、sin(-X) = -sinX, cos(-X) = conX による変換も行われる。

次に、+α分回転させた、A'(cos(-α + α), sin(-α + α)) = (1, 0), B'(cos(β + α), sin(β + α)) を考える。
線分 A'B' の長さの 2 乗は、回転させるまえの線分 AB の長さの 2 乗に一致することにより求めるようだ。

さらに、加法定理より積和公式を求める。
  • しんたすしんはにしんこう
  • こうたすこうはにこしん
は、積和公式の変形の和積公式のようだ。
 cos の加法定理、

の2つの式を足して、

が積和公式の一つ。

ここで、α = x, β = x とすると、

となりようやく本題の式に代入可能な形になった。


1/2 は外に出せるはず。で積分だが、+1 の積分は、+x となるのはいいが、問題は cos2x の部分。
cos x であれば、sin x なわけだが・・・
で調べてみると、合成関数の積分なるものを使うっぽい。
t = 2x として、cos t とする。これを t で積分すると sin t。ただこれは t の積分なので
x でやるためには、 t を微分したものを用意して、

で割ってやると

となり、

やっと繋がった・・・
すごい遠回りをしてる気もする。意外とあっさり変換できたりして・・・

2011年11月22日火曜日

ASUS UX31E-RY128 の感想

しばらく使用してみて、(というかほとんど使用していないが・・・)気づいたこと。

タッチパッド
タッチパッドの反応が悪い時がある。いくら触っても反応しない。 いきなり不具合!?または微妙な仕様!?
かとあせったがとりあえずマニュアルを見たところ、キーボード操作中はタッチパッドの反応領域が狭くなるらしい。
つまり、キーボード操作直後は、反応領域が狭いため、一定の部分からタッチしないと反応しない。
 
「コントロールパネル」の「マウス」より設定をみたところ、範囲のサイズと中心点を設定できるようだ。
キーボード入力中にパッドに触るとクリック扱いとなって、別ウィンドウがアクティブになったりしていたので範囲を小さくした。これは便利だ!
ノートパソコンに慣れていないので最近では当然の機能なのかもしれないが・・・

また、タッチパッドの下に、左クリック、右クリック用のボタンが 2 つあるが、これを使わずともタッチパッドのジェスチャーで右クリックやドラッグ、スクロール、ズーム等の処理ができる。タッチパネルの影響なんだろうか。

キーボード
キーボードの操作感だが、普段メカニカルな感じが好きなため、ノートのキーボードはあまり好きではないが、ノートにしては結構クリック感があるほうだと思う。悪くないかと。

重量
持ち歩いた感じ、やはり本などと一緒に持ち運ぶと重い・・・クッション入りのバッグも欲しいが、それだけで 1kg とかするので迷い中。壊れては意味がないがデフォルトのケースはあるので。
電車で座れずに立ったままブラウジング等もやってみたが、立ちながらやるには大きい。腕も疲れる。
さすがにタブレットのようにはいかなかった。座れる場合は問題ないが。

解像度
解像度は結構満足できるレベル。ブラウジングやちょっとした入力なら何の問題もない。
この記事もノートでやっているがいい感じ。


現時点では、こんなもんか。もうちょっと使ってみれば色々でるかもしれないが。


ノートより通勤途中の地下鉄で WiMax が繋がらないのが痛い。駅停車時ぐらい繋がるのかと思ってたのが甘かった・・・

2011年11月14日月曜日

Jericho HTML で一部サイトが文字化け

前回のソースで一部サイトが文字化けを起こした。

Souce のコンストラクタに URLを渡した場合は、ヘッダー情報の Content-Type の charset からエンコードが取得できるかもだが(Jericho が取得しているかは未調査)、BODY 部分のストリームを渡す場合、取得出来ないため内容から自動判別すようで、そこがおかしくなっているようだった。
なので指定してやることに、
HttpClient http = new DefaultHttpClient();
HttpResponse res = http.execute(new HttpGet(URI.create("http://www.example.com")));
if (res.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
{
  LOG.error("Failed to load uri.");
  return false;
}    

InputStream in = res.getEntity().getContent();
if (res.getHeaders("Content-Encoding").length > 0 &&
  "gzip".equals(res.getHeaders("Content-Encoding")[0].getValue()))
  in = new GZIPInputStream(in);

String charset = EntityUtils.getContentCharSet(res.getEntity());

Source source = null;
if (charset != null) source = new Source(new InputStreamReader(in, charset));
else source = new Source(in);
14 行目で Content-Type の charset からエンコードを取得して、指定があれば、17 行明で明示的に指定してやることに。直接渡しているので、Shift_JIS 等で部分的に文字化けがおきそうだがとりあえず。

Source(final Reader reader) の実装が、
this(reader,(reader instanceof InputStreamReader) ? ((InputStreamReader)reader).getEncoding() : null);
となっていて、instanceof が気に入らない。Source(final Reader reader, final String encoding) が public だといいのだが。

Content-Type の charset がなければ今まで通り自動判別。

もっとスマートにいかないものか・・・