antでZipExceptionが起きる
antを作っていたらなぜかZipExceptionが発生してしまった。
Unable to obtain resource from XXXXX.properties:
java.util.zip.ZipException: error in opening zip file
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.(ZipFile.java:214)
at java.util.zip.ZipFile.(ZipFile.java:144)
at java.util.jar.JarFile.(JarFile.java:152)
調べてみたら
antは
具体的に言うと、もともとこう書いていた。
<path id="run.classes"> <path path="${classes}"/> <fileset dir="${lib.dir}"> <include name="**/*.jar"/> </fileset> </path> <path id="conf"> <fileset dir="${resource.dir}"> <include name="**/*.properties"/> </fileset> </path>
これではconfを参照した際にZipExceptionが起きてしまう。
これをこう書くとうまくいった。
<path id="run.classes"> <path path="${classes}"/> <fileset dir="${lib.dir}"> <include name="**/*.jar"/> </fileset> </path> <path id="conf"> <pathelement location="${resource.dir}"/> </path>
POIを使ってExcelの不要な名前定義を削除する
以前vbaを使った参照できなくなった名前定義の削除を作ったが、
お粗末なものだったのと、JavaでExcelを操作する練習も兼ねて
Apache POIを利用したものを作成してみた。
JavaのライブラリでExcelを扱うものはJExcelApiとApache POIがある。
今回はApache POIを使って作成した。
# というか最初はJExcelApiで作っていたのだが不要な(参照できない)名前定義を読み込めないようなので断念した。
実装した仕様としては下記。
・エクセルの名前定義を読込み、参照できないもの(#REF)を削除して、ファイルを保存する。
・名前定義を削除しない場合はファイルの保存を行わない。
・ディレクトリ指定をすると、xls拡張子のファイルを探し出し、処理を行う。(サブディレクトリを含む)
・削除した名前定義を標準出力してログとする。
で、結局簡単にできた。
vbaで作成した時と違うのはどうもPOIでは別ファイルへの参照はnullになってしまうようだ。
# vbaで"Document ant Setting"のパスを意識して削除していたが、そこがnull。
# なので特に名前定義の参照先を意識せずに"参照できなかったら削除"というロジックになっている。
まぁ使いたいだけの人には不要な説明はソースを配布することで置いておいて、
実行の仕方について説明する。
1. 名前定義削除JarファイルをDL。ここからDLできます。
2. Apache POIをDL。最新は3.8だが、3.7を使用。
3. ファイル操作で使用するApache Commons IOライブラリ。ver.2.0.1。
# また、このプログラムは現時点で最新のJava7で作成されているため、JREのVersionUpも必要かもしれない。
上記3つのJarファイルをクラスパスに入れて
excel.poi.ExcelRmRefNamesPoiを実行する。
その際に引数として、処理させたいディレクトリパスを指定する。
# 注意点:Windowsの場合はパス区切り文字を円マーク"\"ではなく、スラッシュ"/"へ変換してください。
java -cp "ExcelRmRefNames.jar;poi-3.7-20101029.jar;commons-io-2.0.1.jar" excel.poi.ExcelRmRefNamesPoi [TargetDir]
また、読み込み専用ファイルやPOIで読み込めないファイルであった場合は失敗し、エラーログを吐いたのち、そのまま次のファイルを続けて処理する。
使用にあるとおりサブディレクトリも全て走査し.xlsファイルを処理する。
注意:必ずバックアップを取った後に実行することをお勧めします。
ライセンスは、GPLとします。
# ライセンスについて触れたのは初めてのことなのでなにか不備があるかもしれません。
# コメントで指摘してもらえると助かります。
XStreamではXML宣言はつけられない
XMLにはXML宣言というものがあるが、XStreamではXStreamAPIを使ってXML宣言を作ることはできない。
# たぶんXStreamの入出力に必要ないからだと思う。
無いと困る場面もあるため、その場合は単にStringの結合で作ってしまったほうが簡単で良い。
public static void main(String[] args) { String header = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"; String xml = SerializingXML.makeXml(); System.out.println(header+"\n"+xml); }
# 先日作ったシリアライズクラスを外部から呼び出せるように変更して、XML文字列を作成した。
処理結果
<?xml version="1.0" encoding="UTF-8" ?> <settings config="settings-template.xml"> <server> <id>codehaus-nexus-snapshots</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> <server> <id>codehaus-nexus-staging</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> <server> <id>codehaus.org</id> <username></username> <password></password> </server> </settings>
まぁ単にこれだけですが。
例えばSOAP通信の際に必要となるHEADER情報もこの形で出しても問題ないと思っている。
# 改行の部分については改良の余地あり。
KXml2を使う
XStreamは内部でKXml2を利用していることは既に書いた。
今回はKXml2単体での利用法を簡単に紹介しようと思う。
しかし残念ながらKXml2自体のSampleにはあまり多くのことは書かれていない。
それは次のことから不要と判断されたのだと考えている。
まず、KXml2はXmlPullをベースにしているということを思い出して欲しい。
KXml2のParserを見てみるとそのことがよくわかる。
public class KXmlParser implements XmlPullParser {
XmlPullParserインターフェースをimplementsしている。
つまりKXmlの使用法はXmlPullとほぼ同様だろうということが分かったはずだ。
次にXmlPullのSampleをあさってみると、いいものがあった。
/xmlpull_1_1_3_4c/src/java/samples/SimpleXmlPullApp.java
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/ // for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/) import java.io.IOException; import java.io.StringReader; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; /** * Very simple application that demonstrates basics of XMLPULL V1 API. * * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a> */ public class SimpleXmlPullApp { public static void main (String args[]) throws XmlPullParserException, IOException { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); //factory.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); XmlPullParser xpp = factory.newPullParser(); System.out.println("parser implementation class is "+xpp.getClass()); xpp.setInput ( new StringReader ( "<foo>Hello World!</foo>" ) ); int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if(eventType == XmlPullParser.START_DOCUMENT) { System.out.println("Start document"); } else if(eventType == XmlPullParser.END_DOCUMENT) { System.out.println("End document"); } else if(eventType == XmlPullParser.START_TAG) { System.out.println("Start tag "+xpp.getName()); } else if(eventType == XmlPullParser.END_TAG) { System.out.println("End tag "+xpp.getName()); } else if(eventType == XmlPullParser.TEXT) { System.out.println("Text "+xpp.getText()); } eventType = xpp.next(); } } }
なるほど、XmlPullってのはこうやってNodeを走査するんだな。
KXmlParserはXmlPullParserをimplementsしているので、
上記と同様にXmlPullParser.START_DOCUMENTだとかが使えるわけだ。
あえてKXml2で書き換えるとこうなる。
public static void main (String args[]) throws XmlPullParserException, IOException { KXmlParser xpp = new KXmlParser(); System.out.println("parser implementation class is "+xpp.getClass()); xpp.setInput ( new StringReader( "<foo>Hello World!</foo>" ) ); int eventType = xpp.getEventType(); while (eventType != KXmlParser.END_DOCUMENT) { if(eventType == KXmlParser.START_DOCUMENT) { System.out.println("Start document"); } else if(eventType == KXmlParser.END_DOCUMENT) { System.out.println("End document"); } else if(eventType == KXmlParser.START_TAG) { System.out.println("Start tag "+xpp.getName()); } else if(eventType == KXmlParser.END_TAG) { System.out.println("End tag "+xpp.getName()); } else if(eventType == KXmlParser.TEXT) { System.out.println("Text "+xpp.getText()); } eventType = xpp.next(); } }
# インデントが気持ち悪かったので変えた。
処理結果
parser implementation class is class org.kxml2.io.KXmlParser
Start document
Start tag foo
Text Hello World!
End tag foo
KXmlParserはFactoryクラスを必要としないのでなんだかさっぱりしていいような気がしてきた。
速度としては後続であるKXml2が勝るのだろう。
DOMやSAXを触ったことのある人なら上記Sampleで大体の感覚は掴めたことだろうと思う。
XStreamを使う
一時期お世話になったXStreamについて紹介したいと思う。
XStreamは、オブジェクトをXMLにシリアライズしたり、逆にデシリアライズするシンプルなXML操作ライブラリである。
XStreamは標準ではkXML2という軽量で高速なXML parserを使用する。
しかし依存関係を作りたくない場合はJavaのDOM parser(javax.xml.parsers.DocumentBuilder)や、
Java6から追加されたStreaming API for XML、通称 StAXも利用できる(javax.xml.stream.XMLStreamReader)。
※公式チュートリアルではxstream-[version].jar と kxml2-min-[version].jarをクラスパスに入れること、とあるが、
それではExceptionが発生してしまうので、kxml2-min-[version].jarの代わりに kxml2-[version].jarを使えばいい。
それについては別記事とした。
理解しやすいエイリアスを使ったシリアライズ・デシリアライズを実際にやってみる。
■シリアライズする
○課題XML
作りたいXMLはxstream-distribution-1.4.1-src.zipに入っていた"settings-template.xml"にする。
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (C) 2006, 2007, 2010 XStream committers. All rights reserved. The software in this package is published under the terms of the BSD style license a copy of which has been included with this distribution in the LICENSE.txt file. Created on 30. July 2006 by Mauro Talevi --> <settings> <servers> <server> <id>codehaus-nexus-snapshots</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> <server> <id>codehaus-nexus-staging</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> <server> <id>codehaus.org</id> <username></username> <password></password> </server> </servers> </settings>
○モデルを考える
問題のXMLはsettingsの中にserversという項目があり、serversは複数のserverで構成されている。
モデルとして作成するのはList型のserversを持つsettingsと、3つの項目を持つserverになる。
Settings.java
package xml.xstream.step1; import java.util.ArrayList; import java.util.List; public class Settings { private List<Server> servers = new ArrayList<>(); public void add(Server srv){ servers.add(srv); } public List<Server> get(){ return servers; } @Override public String toString(){ StringBuilder builder = new StringBuilder(); for(Server server : servers) builder.append(server+"\n"); return builder.toString(); } }
Server.java
package xml.xstream.step1; public class Server { private String id; private String username; private String password; public Server(String id,String username, String password){ this.id = id; this.username = username; this.password = password; } @Override public String toString(){ return "id:"+id+", username:"+username+", password:"+password; } }
※コンソール出力しやすいようにtoString()をOverrideしている。
○シンプルなテスト(シリアライズ)
まずは簡単にシリアライズできることを確認する。
シリアライズはXStream#toXML(Object)で簡単にできる。
public class SerializingXML { public static void main(String[] args) { Settings settings = new Settings(); settings.add(new Server("codehaus-nexus-snapshots", "your-xircles-id", "your-xircles-pwd")); settings.add(new Server("codehaus-nexus-staging", "your-xircles-id", "your-xircles-pwd")); settings.add(new Server("codehaus.org", "", "")); XStream xstream = new XStream(); System.out.println(xstream.toXML(settings)); } }
処理結果
<xml.xstream.step1.Settings> <servers> <xml.xstream.step1.Server> <id>codehaus-nexus-snapshots</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </xml.xstream.step1.Server> <xml.xstream.step1.Server> <id>codehaus-nexus-staging</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </xml.xstream.step1.Server> <xml.xstream.step1.Server> <id>codehaus.org</id> <username></username> <password></password> </xml.xstream.step1.Server> </servers> </xml.xstream.step1.Settings>
○Class エイリアス
しかしこのままでは"xml.xstream.step1.Server"などクラス名そのままTag名になってしまっている。
これをClassエイリアスを使って任意のTag名へ変える必要がある。
XStream xstream = new XStream(); xstream.alias("settings", Settings.class); xstream.alias("server", Server.class);
処理結果
<settings> <servers> <server> <id>codehaus-nexus-snapshots</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> ... </servers> </settings>
Tag名が変わった。
目標としてはこれで完成した。
○暗黙のコレクション
しかしこれだけでは物足りないのでImplicit Collections(暗黙的コレクション)も使ってみる。
この機能は問題で言う所の"servers"に対する機能となる。
"servers"はモデルでも定義している通り、List型の構造である。
"servers"Tagを無くすことができる。
xstream.addImplicitCollection(Settings.class, "servers");
処理結果
<settings> <server> <id>codehaus-nexus-snapshots</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> <server> ... </server> <server> ... </server> </settings>
ちなみに、ver1.3まではList以外の配列やMapは使用できなかった。
ver1.4より上記2つも利用できるようになったようだ。
○Field エイリアス
ここでsettingsへファイル名の項目を追加することにする。
Settings.java へ下記を追加
private String configName; public Settings(String confName){ this.configName = confName; }
そしてmainはこうなる
public class SerializingXML { public static void main(String[] args) { Settings settings = new Settings("settings-template.xml"); settings.add(new Server("codehaus-nexus-snapshots", "your-xircles-id", "your-xircles-pwd")); settings.add(new Server("codehaus-nexus-staging", "your-xircles-id", "your-xircles-pwd")); settings.add(new Server("codehaus.org", "", "")); XStream xstream = new XStream(); xstream.alias("settings", Settings.class); xstream.alias("server", Server.class); xstream.addImplicitCollection(Settings.class, "servers"); System.out.println(xstream.toXML(settings)); } }
処理結果
<settings> <configName>settings-template.xml</configName> <server> ... </server> </settings>
しかし、Tag名を"configName"ではなく"config"としたいため、
Fieldエイリアスを使う。
mainに下記を追加
xstream.aliasField("config", Settings.class, "configName");
処理結果
<settings> <config>settings-template.xml</config> <server> ... </server> </settings>
ちゃんと"config"になった。
○Attribute エイリアス
XMLにはValueだけでなく、Attribute(属性)値も存在する。
ここではさきほど追加した"config"を"settings"の属性としたい。
xstream.useAttributeFor(Settings.class, "configName");
処理結果
<settings config="settings-template.xml"> ... </settings>
しっかり属性となった。
簡単なXMLならばここまでの機能を使って作成することができるだろう。
また、ここではSettingsクラスのFieldであるconfigNameを属性に変換したが、
モデル自体を属性としたい場合は独自のConverterを利用することもできる。
この機能についてはまた次の機会に紹介することとする。
■デシリアライズする
今度は逆にXMLファイルをオブジェクトとして持ってみる。
今まで作ってきたモデルを使う。
問題のXMLへconfig属性を追加する。
<settings config="settings-hogehoge.xml">
main
public static void main(String[] args) { Path fullpath = FileSystems.getDefault().getPath( "src","xml","xstream","step1","settings-template.xml"); File full = fullpath.toFile(); XStream xstream = new XStream(); xstream.alias("settings", Settings.class); xstream.alias("server", Server.class); xstream.aliasAttribute(Settings.class, "configName", "config"); Settings settings = (Settings) xstream.fromXML(full); System.out.println("servers.size="+settings.get().size()); System.out.println("settings.configName="+settings.getConfigName()); System.out.println(settings); System.out.println(xstream.toXML(settings)); }
処理結果
servers.size=3 settings.configName=settings-hogehoge.xml id:codehaus-nexus-snapshots, username:your-xircles-id, password:your-xircles-pwd id:codehaus-nexus-staging, username:your-xircles-id, password:your-xircles-pwd id:codehaus.org, username:, password: <settings config="settings-hogehoge.xml"> <servers> <server> <id>codehaus-nexus-snapshots</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> <server> <id>codehaus-nexus-staging</id> <username>your-xircles-id</username> <password>your-xircles-pwd</password> </server> <server> <id>codehaus.org</id> <username></username> <password></password> </server> </servers> </settings>
いろいろと端折るが、特筆すべきはaliasAttributeだろう。
属性を読み込む際は、aliasAttributeをしなくてはならない。
長かった紹介もここでおしまい。
お疲れ様でした。
java.lang.NoClassDefFoundError: org/xmlpull/v1/XmlPullParserFactory
公式チュートリアルではこう書いてある。
You require xstream-[version].jar and kxml2-min-[version].jar in the classpath. kXML2 is a very fast XML pull-parser implementation. If you do not want to include this dependency, you can use a standard JAXP DOM parser or since Java 6 the integrated StAX parser instead:
つまり標準で使用する場合は、XStreamのJarとkXML2-min版Jarをクラスパスに入れろ、ということらしい。
しかし、実際に[DownLoad]にあるStable Version: 1.4.1 "Binary distribution"をDLして
上記Jarをクラスパスに入れて実行すると、
Exception in thread "main" java.lang.NoClassDefFoundError: org/xmlpull/v1/XmlPullParserFactory
と言われてしまう。
kxml2-min-2.3.0.jarの中身を覗いてみても、Factoryクラスは入っていない。
$ jar -tf kxml2-min-2.3.0.jar
META-INF/
META-INF/MANIFEST.MF
org/
org/kxml2/
org/kxml2/io/
org/kxml2/io/KXmlParser.class
org/xmlpull/
org/xmlpull/v1/
org/xmlpull/v1/XmlPullParser.class
org/xmlpull/v1/XmlPullParserException.class
どういうことだ?
min版でないkxml2-2.3.0.jarにはちゃんとFactoryクラスなど全部はいっているので、
そちらを使ってやれば問題なく使える。
kxml2本店のほうもみてみた。
kxml2 2.3.0のDLページにあるkxml2-min-2.3.0.jarも上記と同じ構成。
XmlPullParserFactoryが入ってない。
仕方ないので、ソース一式落としてみる。
build.xmlがあったので、見てみる。
<target name="build_jar"> <delete dir="tmpclasses"/> <mkdir dir="tmpclasses"/> <unzip src="lib/xmlpull_1_1_3_1.jar" dest="tmpclasses" overwrite="true"/> <!-- debug="off" --> <javac srcdir="src" verbose="true" destdir="tmpclasses" optimize="true" target="1.2" source="1.2" debuglevel="none"/> <mkdir dir="dist"/> <jar jarfile="dist/kxml2-${version}.jar" > <fileset dir="tmpclasses"> <include name="**/*.class"/> <include name="META-INF/**"/> </fileset> <fileset dir="src"> <include name="META-INF/**"/> </fileset> </jar> <jar jarfile="dist/kxml2-min-${version}.jar"> <fileset dir="tmpclasses"> <!-- <include name="META-INF/**"/> --> <include name="org/xmlpull/v1/XmlPullParser.class"/> <include name="org/xmlpull/v1/XmlPullParserException.class"/> <include name="org/kxml2/io/KXmlParser.class"/> </fileset> </jar> <delete dir="tmpclasses"/> </target>
kxml2はxmlpullというライブラリをベースにしているらしく、
build.xmlでもlib/xmlpull_1_1_3_1.jarを解凍して、
自分のjarに組み込んでいる。
しかし、やはりここでも"org/xmlpull/v1/XmlPullParserFactory"はincludeされない。
<jar jarfile="dist/kxml2-min-${version}.jar"> <fileset dir="tmpclasses"> <!-- <include name="META-INF/**"/> --> <include name="org/xmlpull/v1/XmlPullParser.class"/> <include name="org/xmlpull/v1/XmlPullParserException.class"/> <include name="org/kxml2/io/KXmlParser.class"/> </fileset> </jar>
私の使い方がまずいのか、min版はなにか違うもののためにあるのか、
わからずじまいとなったが、これを見てくれたあなたは通常版を使って快適に過ごしてほしい...。
FileAppender 動的にログファイル名を変える
通常logbackでFileAppenderやRollingFileAppenderを使う際に
ファイル名はfile項目へ設定する必要がある。
しかしこの場合ログファイル名は固定となる。
動的にログファイル名を変更したい場合について紹介したいと思う。
ここではRollingFileAppenderで日次ローテーション(TimeBasedRollingPolicy)する場合。
単純に言うと、 RollingFileAppender と TimeBasedRollingPolicy を
継承したクラスを作成すれば良い。
そしてコンストラクタでファイル名を設定してあげればいいだけ。
MyRollingFileAppenderクラス
package log.logback; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.TriggeringPolicy; public class MyRollingFileAppender extends RollingFileAppender<TriggeringPolicy<ILoggingEvent>>{ public MyRollingFileAppender(){ super(); setFile(getFileName()); } private String getFileName(){ // ここでファイル名を作成する. return "log/testFile.log"; } }
MyTimeBasedRollingPolicyクラス
package log.logback; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; public class MyTimeBasedRollingPolicy extends TimeBasedRollingPolicy<ILoggingEvent> { public MyTimeBasedRollingPolicy(){ super(); setFileNamePattern(getPattern()); } private String getPattern(){ // ここでファイル名パターンを作成する. return "log/testFile.log.%d{yyyy-MM-dd}.log"; } }
※packageは任意。
コンストラクタでRollingFileAppenderではsetFile(String)、TimeBasedRollingPolicyではsetFileNamePattern(String)で設定する。
これら独自クラスをlogback.xmlで使用するようにすればOK。
<configuration> <appender name="FILE" class="log.logback.MyRollingFileAppender"> <!-- <file>logFile.log</file> --> <rollingPolicy class="log.logback.MyTimeBasedRollingPolicy"> <!-- daily rollover --> <!-- <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern> --> <!-- keep 30 days worth of history --> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="FILE" /> </root> </configuration>
ちなみにmaxHistoryもTimeBasedRollingPolicy#setMaxHistory(int)が存在するので、動的に変更可能。
# version 0.9.29ではコンストラクタは1つだけだが、今後コンストラクタが増えた場合はその分同じコンストラクタを作成したほうが無難。
また、この記事ではgetFileName()やgetPattern()メソッドを作成したが、もし別の名前で作成する場合は、
意図しない@Overrideをしないよう注意したい。