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

昔、再帰関数で書いたのかもしれないけど、改めて再帰関数、結局これしか思いつかないけど
久々に 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);

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));

とする。

XStream の CDATAセクションの書き方

XStream で、CDATAセクションを出力するには、XStream のコンストラクタに、HierarchicalStreamDriver 実装の
Driver を指定し、指定する Driver が、createWriter で返す Writer が、直接テキストを判定して書かせるしか
ないみたい。

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

   XStream xstream = new XStream(new XppDriver(){
      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);
               }
            }
         };
      }
   });

   // xstream toXML を実行する

}catch(Exception e){
   e.printStackTrace();
}finally{

}

ダサい。

読込みは特に何もしなくて良い。

PostgreSQL のアップサートとMySQLのアップサート

PostgreSQL に馴染みがなく、アップサートあるいは、SERIAL に困惑している。
MySQL には、AUTO INCREMENT があり、例えば、以下のようなテーブル

CREATE TABLE sample 
(
   id    INT NOT NULL AUTO_INCREMENT,
   point INT,
   price INT,
   PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1

これと同等な PostgreSQL でのテーブルは、

CREATE TABLE sample 
(
   id SERIAL NOT NULL,
   point INT,
   price INT,
   CONSTRAINT sample_pkey PRIMARY KEY (id)
)

制約を見る。。
SELECT table_name, constraint_name, constraint_type
FROM   information_schema.table_constraints
WHERE  table_name='sample';

 table_name |    constraint_name    | constraint_type
------------+-----------------------+-----------------
 sample     | sample_pkey           | PRIMARY KEY
 sample     | 2200_16824_1_not_null | CHECK

まず、MySQLの AUTO INCREMENT 同様、PostgreSQL の 動き、

SELECT * FROM sample;

 id | point | price
----+-------+-------
  1 |    10 |   100
  2 |    20 |   200
(2 行)

ここで、、、INSERT INTO sample (point, price) VALUES (30, 300)
を実行する。MySQL も同じく、PostgreSQL でも、

SELECT * FROM sample;

 id | point | price
----+-------+-------
  1 |    10 |   100
  2 |    20 |   200
  3 |    30 |   300
(3 行)

ここまでは、別に違和感もなく特になにもなく納得。

アップサートを行う!!
まず、存在するレコードに対して、、、
MySQL では、以下のように書く。

INSERT INTO sample (id, `point`, price) VALUES (3, 40, 400)
ON DUPLICATE KEY UPDATE
 `point` = VALUES(`point`)
, price  = VALUES(price)

PostgreSQL では、以下のように書く

INSERT INTO sample (id, `point`, price) VALUES (3, 40, 400)
ON CONFLICT ON CONSTRAINT sample_pkey
DO UPDATE SET point = 40, price = 400

どちらも、id = 3 のレコードだけが更新される。

実践では、動的SQL文生成でこのアップサート文を挿入か更新か処理設計の都合で
実行時にならないと定まらないまま使う。
「アップサート」という本来の目的からすれば、挿入か更新か実行時に判断されることを
期待している。
だから、MySQL では、アップサートが以下のように、制約キー id が NULL の時、、

INSERT INTO sample (id, `point`, price) VALUES (null, 40, 400)
ON DUPLICATE KEY UPDATE
 `point` = VALUES(`point`)
, price  = VALUES(price)

これは、更新でなく新しいレコードが挿入される。AUTO INCREMENT が効いてくる。

しかし、、、PostgreSQL では、、

INSERT INTO sample (id, `point`, price) VALUES (null, 40, 400)
ON CONFLICT ON CONSTRAINT sample_pkey
DO UPDATE SET point = 40, price = 400

ERROR:  列"id"内のNULL値はNOT NULL制約違反です

エラーになり、新しいレコード挿入にならない!!・・・あれ?上に書いたように、

INSERT INTO sample (point, price) VALUES (30, 300)

が成功する PostgreSQL に AUTO INCREMENT の代わりとされてる SERIAL は、何なの?

PostgreSQL のアップサート文で以下のようにすると成功する。

INSERT INTO sample (id, point, price) VALUES (nextval('sample_id_seq'::regclass), 40, 400)
ON CONFLICT ON CONSTRAINT sample_pkey
DO UPDATE SET point = 40, price = 400

これじゃ、実行するSQL生成として、 VALUES 文に、null を入れる代わりに、
nextval('sample_id_seq'::regclass) を入れるなんて、変なことをしなくてはならない。

挿入でも更新でも1つのアップサートSQLでという本来の目的に合わない!

実践の処理として考えると既存データを編集した登録のSQLは、制約キーIDの値が存在するデータで
SQL文にパラメータ渡しで生成するであろうし、
新しいデータである場合、NULL をSQL文生成に渡すことになる。この実行時にならないと
決定しない時に、アップサートのSQL文が威力を発揮するはずで、
PostgreSQL のアップサートとされている文は、動的変化に耐えられない。

PostgreSQL より、MySQL の方に軍配が上がる。

そもそも、そんなDBの比較なんて無意味なのか。。。