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

とする。

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の比較なんて無意味なのか。。。

Google gson fromJson で、 Map<String, Object> への変換は落とし穴。。。

Google gson fromJson を使用する場合、通常変換先は自分で用意するクラスがほとんどのケースであり、
であり、Object に変換させることはあまりやらない。

Gson gson = new GsonBuilder().serializeNulls().create();

Map<String, Object> map = gson.fromJson("{a:0}", new TypeToken<Map<String, Object>>(){}.getType());

System.out.println(map);

この結果は、

{a=0.0}

に、なってしまう。 0 では、なく 0.0 になってしまう。

Map<String, String> map = gson.fromJson("{a:0}", new TypeToken<Map<String, String>>(){}.getType());

であれば、、、

{a=0}

で思いどおりではあるが、Map の value を Objectにしてしまうと、こうならならない。

それでは、Gson のデシリアライザを用意するということで、以下のデシリアライザを用意する。

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
 * GenericMapDeserializer<T>  :  Map<String, T> へのデシリアライザ
 */
public final class GenericMapDeserializer<T> implements JsonDeserializer<Map<String, T>>{
   @Override
   public Map<String, T> deserialize(JsonElement jsonElement, Type typeOfT
, JsonDeserializationContext context) throws JsonParseException{

      if (!jsonElement.isJsonObject()){
         return null;
      }
      JsonObject jsonObject = jsonElement.getAsJsonObject();
      Set<Entry<String, JsonElement>> jsonEntrySet = jsonObject.entrySet();
      Map<String, T> deserializedMap = new HashMap<String, T>();

      for(Entry<String, JsonElement> entry : jsonEntrySet){
         try{
            if(entry.getValue().isJsonNull()){
               deserializedMap.put(entry.getKey(), null);
            }else if(entry.getValue().isJsonArray()){
               deserializedMap.put(entry.getKey(), (T)entry.getValue());
            }else if(entry.getValue().isJsonObject()){
               deserializedMap.put(entry.getKey(), (T)entry.getValue());
            }else if(entry.getValue().isJsonPrimitive()){
               deserializedMap.put(entry.getKey(), context.deserialize(entry.getValue(), String.class));
            }
         }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
         }
      }
      return deserializedMap;
   }
}

これを GsonBuilder で Gson を作るとき、registerTypeAdapter で指定する。

Gson gson = new GsonBuilder()
.registerTypeAdapter(new TypeToken<Map<String, Object>>(){}.getType(),new GenericMapDeserializer<Object>())
.serializeNulls()
.create();

こうして、、

Map<String, Object> map = gson.fromJson("{a:0}", new TypeToken<Map<String, Object>>(){}.getType());

System.out.println(map);

とすれば、、、

{a=0}

が結果になる。

XStream null value を出力させるケース、修正

XStream null value を出力させるケース、先日の 
XStream を使うかどうかは、NULL value をどう扱うかが問題 - Oboe吹きプログラマの黙示録
の方法は、やはり良くない!!

書込み専用になってしまうからである。やはり、ReflectionConverterを継承して doMarshal を null 対応したものを
XStream の registerConverter メソッドで登録するしかない。

そこで、以下の CustomReflectionConverter を用意する。

import java.util.function.BiConsumer;
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;
/**
 * CustomReflectionConverter
 */
public class CustomReflectionConverter extends ReflectionConverter{
   private BiConsumer<Object, HierarchicalStreamWriter> consumer;
   public CustomReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider
                                   , BiConsumer<Object, HierarchicalStreamWriter> onMarchall){
      super(mapper, reflectionProvider);
      consumer = onMarchall;
   }
   protected void doMarshal(final Object source, final HierarchicalStreamWriter writer
                            , final MarshallingContext context) {
      super.doMarshal(source, writer, context);
      consumer.accept(source, writer);
   }
}

XStream を使うかどうかは、NULL value をどう扱うかが問題 - Oboe吹きプログラマの黙示録 で書いたように、

XML出力時専用のフィールドを文字列で用意なんてしない!!

public class Root{
   @XStreamAlias("date")
   @XStreamConverter(value=CustomLocalDateConverter.class, strings={"yyyy/MM/dd"})
   public LocalDate birthday;
     :
     :

このCustomReflectionConverter を registerConverterで指定する。

XStream stream = new XStream(new DomDriver("UTF-8"));
CustomReflectionConverter reflectionConverter
= new CustomReflectionConverter(stream.getMapper(), new SunUnsafeReflectionProvider(), (o, w)->{
   if (o instanceof Root){
      if (((Root)o).date==null){
         w.startNode("date");
         w.setValue("");
         w.endNode();
      }
   }
});
stream.registerConverter(reflectionConverter, XStream.PRIORITY_VERY_LOW);
stream.processAnnotations(Root.class);

try(OutputStream out = new FileOutputStream(new File("out.xml"))){
   out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".getBytes());
   stream.toXML(root, out);
}catch(Exception e){
   e.printStackTrace();
}finally{
   System.out.println("finish!");
}


Root の date が null の場合、Rootタグの最後になってしまうが、、

<?xml version="1.0" encoding="UTF-8"?>
<root>
      :
  <date></date>
</root>

が作れる。null でない場合は、@XStreamConverterアノテーションで書いたコンバータが使用される。