tar and zip の圧縮・展開

昨日書いた FileCollection は、実はこの tar and zip の圧縮・展開 を書く為の伏線です。
oboe2uran.hatenablog.com

Apache commons compress を使用して
https://commons.apache.org/proper/commons-compress/

関数型インターフェースで圧縮・展開を用意した。
圧縮対象、複数のケースも考慮して Supplier で対象コレクションを指定するのである。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Supplier;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
/**
 * tar and gzip 圧縮/解凍プロセッサ.
 */
public interface TarGzipProcessor extends Supplier<Collection<FileCollection>>{
   /**
    * tar and gzip 圧縮実行.
    * @param targzPath 作成する tar and gzip ファイルパス、 *.tar.gz
    * @return tarエントリ名 Collection
    */
   public default Collection<String> compress(String targzPath){
      Collection<String> entries = new ArrayList<>();
      String tarpath = targzPath.replaceAll("\\.tar\\.gz$", ".tar");
      // tar 生成
      try(FileOutputStream out = new FileOutputStream(tarpath);
         TarArchiveOutputStream taos = new TarArchiveOutputStream(out)
      ){
         get().forEach(fc->{
            String prefix = fc.getFile().getParentFile().getAbsolutePath().replaceAll("\\\\", "/");
            fc.scan(f->{
               try{
                  if (f.isDirectory()){
                     TarArchiveEntry entry = new TarArchiveEntry(fc.getFile()
                     , f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
                     taos.putArchiveEntry(entry);
                     taos.closeArchiveEntry();
                     entries.add(entry.getName());
                     return;
                  }
                  TarArchiveEntry entry 
                  = new TarArchiveEntry(f, f.getAbsolutePath().replaceAll("\\\\", "/").replaceFirst(prefix, ""));
                  taos.putArchiveEntry(entry);
                  //taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
                  taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
                  try(FileInputStream fis = new FileInputStream(f);
                     BufferedInputStream bis = new BufferedInputStream(fis)
                  ){
                     int size = 0;
                     byte[] buf = new byte[1024];
                     while((size = bis.read(buf)) > 0){
                        taos.write(buf, 0, size);
                     }
                  }
                  taos.closeArchiveEntry();
                  entries.add(entry.getName());
               }catch(IOException ex){
                  throw new RuntimeException(ex);
               }
            });
         });
      }catch(IOException ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
      // gzip 生成
      try(FileInputStream fis = new FileInputStream(tarpath);
         BufferedInputStream bis = new BufferedInputStream(fis);
         FileOutputStream fos = new FileOutputStream(targzPath);
         GzipCompressorOutputStream gout = new GzipCompressorOutputStream(fos)
      ){
         int size = 0;
         byte[] buf = new byte[1024];
         while((size = bis.read(buf)) > 0){
            gout.write(buf, 0, size);
         }
      }catch(IOException ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
      new File(tarpath).delete();
      return entries;
   }

   /**
    * tar and gzip 展開.
    * @param targzPath tar and gzip ファイルパス、 *.tar.gz
    * @param dirPath 展開先ディレクトリPATH
    * @return 展開された tar エントリ名
    */
   public static Collection<String> decompress(String targzPath, String dirPath){
      Collection<String> entries = new ArrayList<>();
      String tarname = targzPath.substring(targzPath.lastIndexOf("/") + 1).replaceAll("\\.gz$", "");
      // gzip 解凍
      try(FileInputStream fis = new FileInputStream(targzPath);
         GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
         FileOutputStream  fos = new FileOutputStream(dirPath + "/" + tarname)
      ){
         int size = 0;
         byte[] buf = new byte[1024];
         while((size = gin.read(buf)) > 0){
            fos.write(buf, 0, size);
         }
      }catch(IOException ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
      // tar展開
      try(FileInputStream fis = new FileInputStream(dirPath + "/" + tarname);
         TarArchiveInputStream tais = new TarArchiveInputStream(fis)
      ){
         ArchiveEntry entry = null;
         while((entry = tais.getNextEntry()) != null){
            File file = new File(dirPath + "/" + entry.getName());
            if (entry.isDirectory()){
               file.mkdirs();
               entries.add(entry.getName());
               continue;
            }
            if (!file.getParentFile().exists()){ file.getParentFile().mkdirs(); }
            try(FileOutputStream fos = new FileOutputStream(file);
                  BufferedOutputStream bos = new BufferedOutputStream(fos)){
               int size = 0;
               byte[] buf = new byte[1024];
               while((size = tais.read(buf)) > 0){
                  bos.write(buf, 0, size);
               }
               entries.add(entry.getName());
            }
         }
      }catch(IOException ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
      new File(dirPath + "/" + tarname).delete();
      return entries;
   }
   /**
    * GZIP解凍実行.
    * @param gzipPath gzip ファイルPATH   *.gz
    * @param dirPath 展開先ディレクトリPATH
    */
   public static void openGz(String gzipPath, String dirPath){
      String tarname = gzipPath.substring(gzipPath.lastIndexOf("/") + 1).replaceAll("\\.gz$", "");
      try(FileInputStream fis = new FileInputStream(gzipPath);
         GzipCompressorInputStream gin = new GzipCompressorInputStream(fis);
         FileOutputStream  fos = new FileOutputStream(dirPath + "/" + tarname)
      ){
         int size = 0;
         byte[] buf = new byte[1024];
         while((size = gin.read(buf)) > 0){
            fos.write(buf, 0, size);
         }
      }catch(IOException ex){
         throw new RuntimeException(ex.getMessage(), ex);
      }
   }
}

Windowsでも使えるように replaceAll が入ってしまった。。


1つのディレクトリを指定し、その配下を tar.gz にする例、

FileCollection fileCollection = FileCollection.of("/home/aaa");
TarGzipProcessor processor = ()->Arrays.asList(fileCollection);
processor.compress("/home/aaa.tar.gz");

複数対象にした圧縮。。。"/home/aaa/" ディレクトリ配下を対象にする場合、
= tarエントリに、親ディクトリ "aaa" を含めさせたくない場合である。

List<FileCollection> fileCollections = Arrays.stream(new File("/home/aaa").listFiles())
.map(e->FileCollection.of(e.getAbsolutePath()))
.collect(Collectors.toList());
TarGzipProcessor processor = ()->fileCollections;
processor.compress(targzipPath);


いきなり、Supplier を使うから、こんな書き方

TarGzipProcessor processor = ()->fileCollections;

で、古くから Java に親しんできた者には、まるで Java のコードでないように一瞬見えてしまいますが、
これが、なんとも簡潔であると気づく。。

FileCollection

昨日の続き、、ディレクトリを走査するもの、名前が良くないので、FileCollection と名付け
さらに、Predicate を走査ではさみたくなった。

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
 * ファイルコレクション.
 */
public final class FileCollection {
   private File file;
   private FileCollection(String path){
      file = new File(path);
   }
   /**
    * インスタンス取得.
    * @param path コレクションするファイルパス
    * @return FileCollection
    */
   public static FileCollection of(String path){
      return new FileCollection(path);
   }
   /**
    * @return file
    */
   public File getFile(){
      return file;
   }
   /**
    * 走査実行→コレクション取得.
    * @return List<File>
    */
   public List<File> scan(){
      return parse(file, new ArrayList<>());
   }
   /**
    * Predicate走査実行→コレクション取得.
    * @param p 検査するPredicate<File>
    * @return List<File>
    */
   public List<File> scan(Predicate<File> p){
      return parse(file, new ArrayList<>(), p);
   }
   /**
    * 走査 Consumer実行.
    * @param c Consumer
    */
   public void scan(Consumer<File> c){
      parse(file, c);
   }
   /**
    * Predicate走査 Consumer実行.
    * @param p 検査するPredicate<File>
    * @param c Consumer
    */
   public void scan(Predicate<File> p, Consumer<File> c){
      parse(file, p, c);
   }
   /**
    * Predicate検査一致の成否.
    * @param p 検査するPredicate<File>
    * @return true=1つ以上一致するものがある。false=一致するものが存在しない。
    */
   public boolean anyMatch(Predicate<File> p){
      return findmatch(file, p);
   }
   private List<File> parse(File file, List<File> list){
      list.add(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, list);
         }
      }
      return list;
   }
   private void parse(File file, Consumer<File> c){
      c.accept(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, c);
         }
      }
   }
   private List<File> parse(File file, List<File> list, Predicate<File> p){
      if (p.test(file)) list.add(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, list, p);
         }
      }
      return list;
   }
   private void parse(File file, Predicate<File> p, Consumer<File> c){
      if (p.test(file)) c.accept(file);
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            parse(f, p, c);
         }
      }
   }
   private boolean findmatch(File file, Predicate<File> p){
      if (p.test(file)) return true;
      if (file.isDirectory()){
         for(File f:file.listFiles()){
            if (findmatch(f, p)) return true;
         }
      }
      return false;
   }
}

Example : /var 配下、ls -R オプションのように全ての log ファイルを抜き出す。

FileCollection.of("/var").scan(p->p.getName().endsWith(".log"), f->{
   System.out.println(f.getAbsolutePath());
});

Example : /var 配下、1個でも "error" がつくファイル名が存在したら何かする

if (FileCollection.of("/var").anyMatch(f->f.getName().indexOf("error") >= 0)){
   // TODO
}

ディレクトリツリーを走査を再興

昔、再帰関数で書いたのかもしれないけど、改めて再帰関数、結局これしか思いつかないけど
久々に for 文を書いてみました。(Java8 になってから、本当に for文を書く機会が減った。。。)

要の再帰関数は、、

private List<File> parse(File file, List<File> list){
   list.add(file);
   if (file.isDirectory()){
      for(File f:file.listFiles()){
         parse(f, list);
      }
   }
   return list;
}

という誰でもすぐに思いつくものです。File の List を取得することにすれば、
List → Stream や、好きなことをできるので、これだけでもいいのですが、
Consumer の形も魅力的です。

private void parse(File file, Consumer<File> c){
   c.accept(file);
   if (file.isDirectory()){
      for(File f:file.listFiles()){
         parse(f, c);
      }
   }
}

そこで以下のとおり全体を書きました。

oboe2uran.hatenablog.com

Tomcat 8.x + Wicket8 で WebSocket native

Tomcat 8.x + Wicket8 で WebSocket native がなかなかうまくできず苦労していたのだが、
やっと解った。。

Tomcat 7.x + Wicket7 では、WebSocketFilter と WicketFilter を web.xml で2つ書いてURL
による切り分けをしてたのですが、
Tomcat 8.x + Wicket8 では、できません。これが長い間、苦戦した原因だった。

よって、org.apache.wicket.protocol.http.WicketFilter と、
Wicket8 から、
  org.apache.wicket.protocol.ws.tomcat7.Tomcat7WebSocketFilter ではなく、
org.apache.wicket.protocol.ws.javax.JavaxWebSocketFilter

WicketFilter と JavaxWebSocketFilter の2つフィルタの宣言はダメなのである。

<filter-name>WebFilter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class><filter-name>WebSocketFilter</filter-name>
<filter-class>org.apache.wicket.protocol.ws.javax.JavaxWebSocketFilterr</filter-class>
<filter-mapping>
   <filter-name>WebFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
   <filter-name>WebSocketFilter</filter-name>
   <url-pattern>/wicket/*</url-pattern>
</filter-mapping>

というのは、成立しないのである。


JavaxWebSocketFilter だけにしなければならない。。

<filter>
   <filter-name>WebFilter</filter-name>
   <filter-class>org.apache.wicket.protocol.ws.javax.JavaxWebSocketFilter</filter-class>
   <init-param>
      <param-name>applicationClassName</param-name>
      <param-value>org.sample.SampleApplication</param-value>
   </init-param>
</filter>

<filter-mapping>
   <filter-name>WebFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

私は、細かく分類、分岐させるWebSocket までは必要なくブロードキャストで充分なので、

https://ci.apache.org/projects/wicket/guide/6.x/guide/nativewebsockets.html

にあるような、WebApplication でWebSocketResourceを登録、、
  getSharedResources().add("someName", new MyWebSocketResource());
ここまでは実行しない。どうもこのキー名を登録した後の、キーに限定させた
WebSocket 通信方法がよくわからない。
でも、ブロードキャストは成功した。
また、上のガイドのページでは、
 page.add(new BaseWebSocketBehavior("someName"));
と書いてあるこれが役にたたない。

pom.xml も、

Tomcat 7.x + Wicket7 では、、

<dependency>
   <groupId>org.apache.wicket</groupId>
   <artifactId>wicket-native-websocket-tomcat</artifactId>
   <version>7.6.0</version>
</dependency>
<dependency>
   <groupId>org.apache.wicket</groupId>
   <artifactId>wicket-native-websocket-javax</artifactId>
   <version>7.6.0</version>
</dependency>


Tomcat 8.x + Wicket8 の pom.xml

<dependency>
   <groupId>org.apache.wicket</groupId>
   <artifactId>wicket-native-websocket-javax</artifactId>
   <version>8.0.0-M6</version>
</dependency>

wicket-native-websocket-javaxだけにする。


受信側 WebPage の記述。。。。

コンストラクタで、WebSocketBehaviorを追加

add(new WebSocketBehavior(){
   @Override
   protected void onConnect(ConnectedMessage message){
      super.onConnect(message);
   }
   @Override
   protected void onMessage(WebSocketRequestHandler handler, TextMessage message){
   }
   @Override
   public void onException(Component component, RuntimeException exception){
      logger.warn(exception.getMessage(), exception);
   }
});

add(new WebSocketBehavior(){} ); でも良いのだが、onConnect、 onException ぐらいは捕捉したい。
受信してもこのビヘビアのonMessage で捕捉できるわけではない!!

受信は、Webページに onEvent メソッドオーバーライドで捕捉する。

@Override
public void onEvent(IEvent<?> event){
   if (event.getPayload() instanceof WebSocketPushPayload){
      WebSocketPushPayload wsEvent = (WebSocketPushPayload)event.getPayload();
      WebSocketRequestHandler handler = wsEvent.getHandler();

      // FeedMessage という任意の受信データを受け取る
      FeedMessage feed = (FeedMessage)wsEvent.getMessage();

      // 任意の処理。。。

      // ページに書いたコンテナ更新
      receive_container.modelChanged();
      handler.add(receive_container);
      handler.appendJavaScript("receiveContainer();");
   }
}

WebSocketRequestHandler#getMessage() で受信データを参照する。
event.getType().name() でイベント種別も確認できる。


クライアント側。。。。

JavaScript ではなく、送信用のページのフォーム送信で、以下のように書く、

WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(getApplication());
WebSocketPushBroadcaster broadcaster = new WebSocketPushBroadcaster(webSocketSettings.getConnectionRegistry());
broadcaster.broadcastAll(getApplication(), message);

favicon.ico resource 404 status

Webサーバを起動後、作成したページにアクセスして、以下のように
ブラウザ側でエラーが出る場合の対処方法、、

failed to load resource the server responded with a status of 404
...
favicon.ico

生成してるHTMLヘッダに、以下を追記する。

<link rel="shortcut icon" href="">

XStream null value を出力するケース、再び書き直す。

先日書いた、カスタムのコンバータで、XStream でXML出力する時の NULL の値のタグを書くケース、
HierarchicalStreamWriter を BiConsumer で渡して書かせるなんてやはりセンスないので、、、


oboe2uran.hatenablog.com

先日の方法ではなく、NULL で空タグを書かせるべきかどうかの Fucntionラムダ関数にした方が良い。

import java.util.function.Function;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
/**
 * CustomEmptyConverter
 */
public class CustomEmptyConverter extends ReflectionConverter{
   private Function<Object, String> emptywriter;
   /**
    * コンストラクタ.
    */
   public CustomEmptyConverter(Mapper mapper, Function<Object, String> emptywriter){
      super(mapper, new SunUnsafeReflectionProvider);
      this.emptywriter = emptywriter;
   }
   @Override
   protected void doMarshal(final Object object, final HierarchicalStreamWriter writer, final MarshallingContext context){
      super.doMarshal(object, writer, context);
      String tagname = emptywriter.apply(object);
      if (tagname != null){
         writer.startNode(tagname);
         writer.setValue("");
         writer.endNode();
      }
   }
}


サンプル

XStream stream = new XStream(new DomDriver("UTF-8"));

CustomEmptyConverter emptyConverter = new CustomEmptyConverter(stream.getMapper(), e->{
   if (e instanceof Root){
      if (((Root)e).date==null) return "date";
   }
   return null;
});
stream.registerConverter(emptyConverter, XStream.PRIORITY_VERY_LOW);

XStream アンダースコアを含むタグのXML出力、

XStreamは、アンダースコアを含むタグをダブらせて、、2個のアンダースコアにしてしまう!!そういう仕様で規則だ。
これを避けるには、

XStream インスタンス生成を、

       Stream stream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("_-", "_"));

でということだが、問題があって、、、

これと、CDATAセクションを書くようにする方法を

     new DomDriver("UTF-8", new XmlFriendlyNameCoder("_-", "_")){
        public HierarchicalStreamWriter createWriter(Writer out){
         return new PrettyPrintWriter(writer){
            protected void writeText(QuickWriter w, String text){
               if (text.indexOf("<") >= 0
                     || text.indexOf(">") >= 0
                     || text.indexOf("&") >= 0
                     || text.indexOf("!") >= 0
                     || text.indexOf("'") >= 0
                     || text.indexOf("\"") >= 0
                  ){
                  w.write("<![CDATA[");
                  w.write(text);
                  w.write("]]>");
               }else{
                  w.write(text);
               }
            }
         };
      }
    }

と書いてしまうと、CDATA セクションのwriter が優先されて、相変わらず、ダブルのアンダースコアになってしまう!!

しかたなく、XppDriver または DomDriver 継承クラスを準備して以下のものを用意して使う。

import java.io.OutputStreamWriter;
import java.io.Writer;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;

/**
 * CdataDomDriver.
 */
public final class CdataDomDriver extends DomDriver{
   private OutputStreamWriter outwriter;
   /**
    * コンストラクタ.
    * @param writer OutputStreamWriter
    */
   public CdataDomDriver(OutputStreamWriter writer){
      super("UTF-8", new XmlFriendlyNameCoder("_-", "_"));
      outwriter = writer;
   }
   @Override
   public HierarchicalStreamWriter createWriter(Writer out){
      return new PrettyPrintWriter(outwriter, new XmlFriendlyNameCoder("_-", "_")){
         protected void writeText(QuickWriter writer, String text){
            if (text.indexOf("<") >= 0
               || text.indexOf(">") >= 0
               || text.indexOf("&") >= 0
               || text.indexOf("!") >= 0
               || text.indexOf("'") >= 0
               || text.indexOf("\"") >= 0
            ){
               writer.write("<![CDATA[");
               writer.write(text);
               writer.write("]]>");
            }else{
               writer.write(text);
            }
         }
      };
   }
}

あるいは、、、

import java.io.OutputStreamWriter;
import java.io.Writer;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;

/**
 * CdataXppDriver.
 */
public final class CdataXppDriver extends XppDriver{
   private OutputStreamWriter outwriter;
   /**
    * コンストラクタ.
    * @param writer OutputStreamWriter
    */
   public CdataXppDriver(OutputStreamWriter writer){
      super(new XmlFriendlyNameCoder("_-", "_"));
      outwriter = writer;
   }
   @Override
   public HierarchicalStreamWriter createWriter(Writer out){
      return new PrettyPrintWriter(outwriter, new XmlFriendlyNameCoder("_-", "_")){
         protected void writeText(QuickWriter writer, String text){
            if (text.indexOf("<") >= 0
               || text.indexOf(">") >= 0
               || text.indexOf("&") >= 0
               || text.indexOf("!") >= 0
               || text.indexOf("'") >= 0
               || text.indexOf("\"") >= 0
            ){
               writer.write("<![CDATA[");
               writer.write(text);
               writer.write("]]>");
            }else{
               writer.write(text);
            }
         }
      };
   }
}


そして、、、

try(OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("out.xml"), "UTF-8")){
   writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");

   XStream stream = new XStream(new CdataXppDriver(writer));

とする。