[Spring][Maven]実行可能jarを生成するときの落とし穴 その2
fatjarを生成すると当然jarの署名も変わります。なのでchecksumを検証するような行儀の良いjarを含んでいると実行時にSecurityExceptionが吹っ飛んできます。
回避策は以下の通り。署名のマニフェストをjarから除外するのです。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>jp.co.tsr_net.omega.pdf.Main</mainClass> </transformer> <!-- Springの各jarにこれらのファイルが存在するため、マージする。 --> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> <!-- jarを作りなおすため、署名は無効にする --> <transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer"> <resource>.RSA</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer"> <resource>.DSA</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer"> <resource>*.SF</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
[Spring][Maven] 実行可能jarを生成するときの落とし穴
どうやらSpringは各jarにspring.handlersやspring.schemasがあるため、maven-assembly-pluginでfatjarを生成しようとすると、ファイルがコンフリクトしてしまいます。
なのでmaven-shade-pluginを使うのが正解。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>net.nagaseyasuhito.sandbox.Main</mainClass> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
RelativeDateLabel
時刻を「36秒前」とか「15分前」とか「8時間前」のように相対的な経過時間で表示するLabel。24時間以上前は絶対時刻で表示。
自分はだいたいIConverterでやってしまうのだけど、Modelでも出来るよね。どう使い分けるのがいいのかね。
package net.nagaseyasuhito.sandbox; import java.util.Date; import java.util.Locale; import org.apache.wicket.Application; import org.apache.wicket.Localizer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.util.convert.IConverter; import org.apache.wicket.util.convert.converters.AbstractConverter; public class RelativeDateLabel extends Label { private static final long serialVersionUID = 1L; public RelativeDateLabel(String id) { super(id); } public RelativeDateLabel(String id, IModel<?> model) { super(id, model); } public RelativeDateLabel(String id, String label) { super(id, label); } @Override public IConverter getConverter(Class<?> type) { return new AbstractConverter() { private static final long serialVersionUID = 1L; @Override public Object convertToObject(String value, Locale locale) { return null; } @Override public String convertToString(Object value, Locale locale) { long duration = (System.currentTimeMillis() - ((Date) value).getTime()) / 1000; Localizer localizer = RelativeDateLabel.this.getLocalizer(); if (duration < 60) { return localizer.getString("second", RelativeDateLabel.this, Model.of(duration)); } duration /= 60; if (duration < 60) { return localizer.getString("minute", RelativeDateLabel.this, Model.of(duration)); } duration /= 24; if (duration < 24) { return localizer.getString("hour", RelativeDateLabel.this, Model.of(duration)); } return Application.get().getConverterLocator().getConverter(value.getClass()).convertToString(value, locale); } @Override protected Class<?> getTargetType() { return Date.class; } }; } }
RelativeDateLabel.propertiesに
second = ${}秒前
minute = ${}分前
hour = ${}時間前って記述もお忘れなく。
cronで任意の件数をバックアップ
例えばデータベースのバックアップを7日間ごとにやるなら曜日を利用して、
/usr/bin/pg_dump database > database-`/bin/date +\%A`.dump
と書けばよかったけれど、任意の日付間隔の場合どうしようかなあと考えた結果がこれ。
/usr/bin/pg_dump database > database-`expr \`date +%-s\` / 60 / 60 / 24 % [バックアップを取りたい件数]`.dump
1970/01/01からの秒数を日数に直した値の剰余をとればdatabase-0.dumpから連番でローテーションして生成されます。
例えば最新10日分なら
/usr/bin/pg_dump database > database-`expr \`date +%-s\` / 60 / 60 / 24 % 10`.dump
こんな感じにするとdatabase-0.dumpからdatabase-9.dumpが生成されます。どれが最新かはファイル名からは判別しづらいけど、まぁそこは生成された日付をみてください、ということで。
Galileoのミラーアップデートサイト
Galileoのアップデートサイト重すぎ!という訳でミラーサイトに変更。
[Window]->[Preferences]->[Install/Update]->[Available Software Sites]
LocationのURLの
http://download.eclipse.org/
を
http://ftp.yz.yamagata-u.ac.jp/pub/eclipse/
に置換するだけ。サクサクアップデート動きます。
Place Holder(Input Prompt)を表示するIBehavior
TextFieldやTextArea用のPlace Holder(入力がない場合はグレーの文字でヒントが出るやつ)のIBehaviorを書いてみました。 書いた、といってもSimpleAttributeModifierと大して変わらないですが。
jQueryとjQuery.placeholderをこのIBehaviorと同じpackageにおいて使ってください。
使い方は至って単純。 Place Holderを適応したいコンポーネントにPlaceHolderBehaviorをaddしてください。
this.add(new TextField<String>("mailAddress").add(new PlaceHolderBehavior(new Model<String>("メールアドレス"))));
こんな感じです。
package net.nagaseyasuhito.sandbox.behavior; import org.apache.wicket.Component; import org.apache.wicket.ResourceReference; import org.apache.wicket.behavior.AbstractBehavior; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.IHeaderResponse; import org.apache.wicket.model.IModel; public class PlaceHolderBehavior extends AbstractBehavior { private static final ResourceReference JQUERY_JS = new ResourceReference(PlaceHolderBehavior.class, "jquery-1.3.2.min.js"); private static final ResourceReference PLACEHOLDER_JS = new ResourceReference(PlaceHolderBehavior.class, "jquery.placeholder.js"); private static final long serialVersionUID = 1L; private IModel<String> value; public PlaceHolderBehavior(IModel<String> value) { this.value = value; } @Override public void onComponentTag(final Component component, final ComponentTag tag) { if (this.isEnabled(component)) { tag.getAttributes().put("placeholder", this.value.getObject()); } } @Override public void renderHead(IHeaderResponse response) { super.renderHead(response); response.renderJavascriptReference(PlaceHolderBehavior.JQUERY_JS); response.renderJavascriptReference(PlaceHolderBehavior.PLACEHOLDER_JS); } }
enhanceされたclassにも対応するIConverterLocator
HibernateなどORMでentityがenhanceされているとWicketのConverterLocatorはObject.toString()の値を返してしまいます。
Application.newConverterLocator()は、内部にclass名とIConverterのMap
サクっとoverrideしてやりたいところですがConverterLocator.getConverter(Class type)がfinalなため、仕方なくコピペ。
IConverterLocator.getConverter(Class type)内でMap
package net.nagaseyasuhito.sandbox; import java.lang.ref.WeakReference; import java.math.BigDecimal; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.apache.wicket.IConverterLocator; import org.apache.wicket.util.convert.ConversionException; import org.apache.wicket.util.convert.IConverter; import org.apache.wicket.util.convert.converters.BigDecimalConverter; import org.apache.wicket.util.convert.converters.BooleanConverter; import org.apache.wicket.util.convert.converters.ByteConverter; import org.apache.wicket.util.convert.converters.CharacterConverter; import org.apache.wicket.util.convert.converters.DateConverter; import org.apache.wicket.util.convert.converters.DoubleConverter; import org.apache.wicket.util.convert.converters.FloatConverter; import org.apache.wicket.util.convert.converters.IntegerConverter; import org.apache.wicket.util.convert.converters.LongConverter; import org.apache.wicket.util.convert.converters.ShortConverter; import org.apache.wicket.util.convert.converters.SqlDateConverter; import org.apache.wicket.util.convert.converters.SqlTimeConverter; import org.apache.wicket.util.convert.converters.SqlTimestampConverter; import org.apache.wicket.util.lang.Objects; public class ConverterLocator implements IConverterLocator { private class DefaultConverter implements IConverter { private static final long serialVersionUID = 1L; private final WeakReference<Class<?>> type; public DefaultConverter(Class<?> type) { this.type = new WeakReference<Class<?>>(type); } public Object convertToObject(String value, Locale locale) { if (value == null) { return null; } if ("".equals(value)) { if (((Class<?>) this.type.get()) == String.class) { return ""; } return null; } try { return Objects.convertValue(value, this.type.get()); } catch (Exception e) { throw new ConversionException(e.getMessage(), e).setSourceValue(value); } } public String convertToString(Object value, Locale locale) { if (value == null || "".equals(value)) { return ""; } return (String) Objects.convertValue(value, String.class); } } private static final long serialVersionUID = 1L; private final Map<String, IConverter> classToConverter = new HashMap<String, IConverter>(); public ConverterLocator() { this.set(Boolean.TYPE, BooleanConverter.INSTANCE); this.set(Boolean.class, BooleanConverter.INSTANCE); this.set(Byte.TYPE, ByteConverter.INSTANCE); this.set(Byte.class, ByteConverter.INSTANCE); this.set(Character.TYPE, CharacterConverter.INSTANCE); this.set(Character.class, CharacterConverter.INSTANCE); this.set(Double.TYPE, DoubleConverter.INSTANCE); this.set(Double.class, DoubleConverter.INSTANCE); this.set(Float.TYPE, FloatConverter.INSTANCE); this.set(Float.class, FloatConverter.INSTANCE); this.set(Integer.TYPE, IntegerConverter.INSTANCE); this.set(Integer.class, IntegerConverter.INSTANCE); this.set(Long.TYPE, LongConverter.INSTANCE); this.set(Long.class, LongConverter.INSTANCE); this.set(Short.TYPE, ShortConverter.INSTANCE); this.set(Short.class, ShortConverter.INSTANCE); this.set(Date.class, new DateConverter()); this.set(java.sql.Date.class, new SqlDateConverter()); this.set(java.sql.Time.class, new SqlTimeConverter()); this.set(java.sql.Timestamp.class, new SqlTimestampConverter()); this.set(BigDecimal.class, new BigDecimalConverter()); } public final IConverter get(Class<?> c) { while (c != null) { IConverter converter = this.classToConverter.get(c.getName()); if (converter != null) { return converter; } c = c.getSuperclass(); } return null; } @SuppressWarnings("unchecked") public final IConverter getConverter(Class type) { if (type == null) { return new DefaultConverter(String.class); } IConverter converter = this.get(type); while (null == (converter = this.get(type))) { if (type.getSuperclass() != null) { type = type.getSuperclass(); } else { return new DefaultConverter(type); } } return converter; } public final IConverter remove(Class<?> c) { return this.classToConverter.remove(c.getName()); } public final IConverter set(final Class<?> c, final IConverter converter) { if (converter == null) { throw new IllegalArgumentException("CoverterLocator cannot be null"); } if (c == null) { throw new IllegalArgumentException("Class cannot be null"); } return this.classToConverter.put(c.getName(), converter); } }
Application.newConverterLocator()をoverrideし、
ConverterLocator converterLocator = new ConverterLocator(); converterLocator.set(User.class, new UserConverter()); return converterLocator;
のように使ってください。