gradleで生成される依存ライブラリの並び順をlibrary名でソートする方法

は、以下の記述をbuild.gradleに追加してください。

eclipse {
    classpath {
        file {
            whenMerged { classpath ->
                def libs = classpath.entries.findAll { it.kind == 'lib' }
                libs = libs.collect { lib ->
                    def baseDir = project.projectDir.getAbsolutePath().replace('\\', '/')
                    if (lib.path.startsWith(baseDir)) {
                        lib.path = lib.path.replace(baseDir, ".")
                    }
                    return lib
                }
                libs.sort(new Comparator<Library>() {
                            public int compare(Library lib1, Library lib2) {
                                String basename1 = new File(lib1.path).getName()
                                String basename2 = new File(lib2.path).getName()
                                return basename1.compareTo(basename2)
                            }
                        })

                def others = classpath.entries.findAll { it.kind != 'lib' }
                classpath.entries = others + libs
            }
        }
    }
}

ただし、eclipseにgradleプロジェクトとして認識させると.classpathを使わずにプラグインの機能を使うようになってしまうため並び順が制御できなくなる。

gradleプロジェクトとしてeclipseに認識させていない場合、以下が不便です。

  • 依存追加時に./grdalew cleanEclipse eclipseで依存ライブラリを取得する必要がある
  • Eclipseからgradleタスクを実行できない(私はCLI派なので困らないけど)
  • Spring Tools使うためには有効化の操作しなきゃいけない

Docker on Centos7 on Vagrant with Chef

概要

掲題の通りのローカル開発環境を構築してみた。

コンセプト

目的

  • RabbitMQやRiakはライブラリでノード指定時に、config.addresses("192.168.33.10","192.168.33.11")みたいな書き方をするので、それをローカル環境でやりたい
  • MySQLは更新系はMasterで、参照系はSlaveなんてケースあるだろうから、Master/Slave構成をローカル環境でやりたい

動かしてるミドルウェア

何をやったのか

後述する参考サイトで、クラスタリングやら固定IPの割付やらは実現されている。
それぞれのdocker-compose.ymlをちょっと修正して、chefのrecipe化して、vagrant upだけで環境を構築できるようにしたところがミソ。
chefでdockerインストールしてdocker-compose upを実行してたりするところもやや難しかった。

成果物

github.com

参考にしたサイト

docker containerへの固定IPの割付

qiita.com coderwall.com

ミドルウェアクラスタリング

RabbitMQ

github.com

Riak

https://hub.docker.com/r/basho/riak-kv/

MySQL

github.com

終わり

誰かの役に立つことを願う。
recipe等の解説は、万が一誰かからリクエストがあれば追記する。

Spring AMQPでオレオレErrorHandlerを使ってみるテスト

@RabbitListeneを使っている時にデフォルトのErrorHandlerではなくて、独自処理を実装したErrorHandlerを使う方法を調べてみたので自分用メモ。

設計も汚いし、とにかく動くところまでしか確認してない。

package hello;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ErrorHandler;

@SpringBootApplication
@EnableRabbit
public class Application implements RabbitListenerConfigurer {

    @Autowired
    ConnectionFactory connectionFactory;

    @Bean
    MessageListenerAdapter listenerAdapter(Receiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setMessageConverter(jackson2Converter());
        return factory;
    }

    @Bean
    public MappingJackson2MessageConverter jackson2Converter() {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        return converter;
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setErrorHandler(errorHandler());
        registrar.setContainerFactory(factory);
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    public ErrorHandler errorHandler() {
        return new MyHandler();
    }

    @Slf4j
    static class MyHandler implements ErrorHandler {

        ErrorHandler errorHandler = new ConditionalRejectingErrorHandler();

        @Override
        public void handleError(Throwable t) {
            log.error("==do something for error==");
            errorHandler.handleError(t);
        }

    }

}

@Service
@Slf4j
class Receiver {

    @RabbitListener(queues = "spring-boot")
    public void receiveMessage(SampleDto dto) {
        log.info("Received <" + dto.toString() + ">");
    }

}

@Data
@NoArgsConstructor
@AllArgsConstructor
class SampleDto {

    private String key;
    private String value;
}

build.gradleはこんな感じ

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.0.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'messaging-rabbitmq-oreore'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile("org.springframework.boot:spring-boot-starter-amqp")
    compile group: 'org.projectlombok', name: 'lombok', version: '1.16.10'

    testCompile("junit:junit")
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

ext.mainClass  = 'hello.Application'

あとテストように作ったpublisherも貼っておく

package hello;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.stereotype.Service;

@SpringBootApplication
public class Application implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.exit(SpringApplication.run(Application.class, args));
    }

    @Bean
    Queue queue() {
        return new Queue("spring-boot", true);
    }

    @Bean
    DirectExchange exchange() {
        return new DirectExchange("direct");
    }

    @Bean
    Binding binding(Queue queue, DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).withQueueName();
    }

    @Bean
    public MappingJackson2MessageConverter jackson2Converter() {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        return converter;
    }

    @Autowired
    private Sender sender;

    @Override
    public void run(String... args) throws Exception {
        for (String i : args) {
            ObjectMapper mapper = new ObjectMapper();
            SampleDto dto = null;
            try {
                dto = mapper.readValue(i, SampleDto.class);
                sender.sendToRabbitmqAsJson(dto);
            } catch (Exception ex) {
                sender.sendToRabbitmqAsString(i);
            }
        }

    }
}

@Service
class Sender {

    @Autowired
    private RabbitMessagingTemplate rabbitMessagingTemplateAsJson;

    @Autowired
    private RabbitMessagingTemplate rabbitMessagingTemplateAsString;

    @Autowired
    private MappingJackson2MessageConverter mappingJackson2MessageConverter;

    public void sendToRabbitmqAsJson(final SampleDto dto) {

        this.rabbitMessagingTemplateAsJson.setMessageConverter(this.mappingJackson2MessageConverter);

        this.rabbitMessagingTemplateAsJson.convertAndSend("direct", "spring-boot", dto);

    }

    public void sendToRabbitmqAsString(final String message) {

        this.rabbitMessagingTemplateAsString.convertAndSend("direct", "spring-boot", message);

    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class SampleDto {

    private String key;
    private String value;
}

rasbperry pi3でNAS構築(とついでにbonding)

ご無沙汰してます。

サーバの勉強にもなるかなと思ってraspberry pi3を手に入れて自作(かつ自宅)NASサーバを構築してみました!

このraspberry pi3なんですけど、無線LAN付きになったんですよ。

USBドングル買わなくても無線化できるので少し財布に優しいー!

さて、そのNASサーバの構築ですがざっくりこんな流れです。

  1. raspberry piをSSHできるようにする
  2. USB HDDをマウントする
  3. smbインストールしてNASにする
  4. 有線LAN+無線LANのbondingの設定をする

この中の(1)〜(3)の手順は検索すればraspberry pi2のものですが情報がザクザク(でもないかもしれないけど)出てくるのでは書くのはやめるとして、、、

(4)の『bondingの設定をする』に絞って手順をご紹介します。

bondingの設定自体はraspberry pi bondingで検索すると、 以下のページが出てくるので、主にこの2つのページを参考に設定しました。

補助記憶: Raspberry PiでUSB-Wifiアダプタを使う

d.hatena.ne.jp

しかーし、紹介されたとおりに設定してみても紹介されている通りに動作しない… どう、動作しないかというと

  1. 上記のページで書かれている通り、bond0にだけIPが振られるわけじゃなかった(eth0とwlan0にもIP振られちゃうけど、無視して設定進めたらbondingは動作した)
  2. LANケーブルさしてないとsshが繋がらない
  3. LANケーブル挿すとping返ってくるけど通信速度が変わらない

という具合です。

tcpdump見たわけじゃないんですが、有線LAN抜くと動作しないってことは、 bonding用のインターフェース(bond0)に割り付けたIPに対するリクエストの返りが有線LANのインターフェース(eth0)から出ていってるんじゃないかと推測して ルーティングテーブル周りの設定変更を試してみたら、うまく動作した!っていうメモです。

たぶん、正しい手順の解決策ではないと思いますが、自分用のメモ(と、他に困ってる人の参考)に参考になったページのリンクを残しておきます。

似たような現象を探してて見つけたページがこちら。

ja.stackoverflow.com

このページを見て、ルーティングテーブルか何かがおかしいのでは、と気づき ip ruleで検索してでてきた

d.hatena.ne.jp

このページに倣って設定したら、動作しました。

こんな感じ!

Before(といいながら有線LAN抜いた時の速度です。スクショ撮り忘れた…)

f:id:kogayushi:20160429151525p:plain

After

f:id:kogayushi:20160429151620p:plain

特定のクラスを継承したクラス一覧を出力する方法

掲題の通り、EclipseとかNetBeansの力を借りずにコードを書いてリストを出してみました。

guavaのClassPath使えばこんな感じで簡単に出せるようです。

package tester;

import com.google.common.reflect.ClassPath;

import java.io.IOException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.apache.struts.action.Action;

/**
 *
 * @author yushi.koga
 */
public class Main {

    public static void main(String... args) {
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            Set<Class<?>> allClasses = ClassPath.from(loader)
                    .getTopLevelClassesRecursive("my.package").stream()
                    .map(info -> info.load())
                    .collect(Collectors.toSet());
            allClasses.stream().filter((actionClass) -> (Action.class.isAssignableFrom(actionClass))).forEach((actionClass) -> {
                System.out.println(actionClass.toString());
            });

        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

RTrim最速っぽいのを簡単に調べてみた

自宅の環境(CPU:Core2 Duo E6750 メモリ4G)ではStringUtils#stripEndが最速らしい。 しかし、会社のPCではguavaが最速でした。

検証したのは、

  • 処理時に(メソッド呼ばれる毎に)で毎回Patternのインスタンス生成からやる
  • Patternの変数をstaticで持たせてインスタンス生成のコストを減らしたもの
  • StringUtils#stripEnd
  • CharMatcher#trimTrailingFrom

のどれが一番早いか!です。

結果はこちら。

検証コード

package speedcheck;

import com.google.common.base.CharMatcher;

import org.apache.commons.lang.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author yushi.koga
 */
public class SpeedCheck {

    static Pattern staticPatternForRtrim = Pattern.compile("\\s+$");
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        String sampleText = "\"aaaaaa\"    ";
        int max = 25000000;

        String staticTrimedText = null;
        System.out.println("original text start from here ->" + "\"aaaaaa\"    " + "<-to end is here");
        System.out.println("loop times : " + max);
        System.out.println("=======================================");
        long staticStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            Matcher staticMatcher = staticPatternForRtrim.matcher(sampleText);
            staticTrimedText = staticMatcher.replaceAll("");
        }
        long staticFinished = System.currentTimeMillis();
        System.out.println("staticStarted      at " + staticStarted);
        System.out.println("staticfinished     at " + staticFinished);
        System.out.println("staticPassed time  is " + ((staticFinished - staticStarted)));
        System.out.println("staticTrimedText   is " + staticTrimedText + "<- end here");
        System.out.println("=======================================");


        String insideTrimedText = null;
        long insideStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            insideTrimedText = Pattern.compile("\\s+$").matcher(sampleText).replaceAll("");
        }
        long insideFinished = System.currentTimeMillis();
        System.out.println("insideStarted      at " + insideStarted);
        System.out.println("insidefinished     at " + insideFinished);
        System.out.println("insidePassed time  is " + ((insideFinished - insideStarted)));
        System.out.println("insideTrimedText   is " + insideTrimedText + "<- end here");
        System.out.println("=======================================");


        String commonsTrimedText = null;
        long commonsStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            commonsTrimedText = StringUtils.stripEnd(sampleText," ");
        }
        long commonsFinished = System.currentTimeMillis();
        System.out.println("commonsStarted     at " + commonsStarted);
        System.out.println("commonsFinished    at " + commonsFinished);
        System.out.println("commonsPassed time is " + ((commonsFinished - commonsStarted)));
        System.out.println("commonsTrimedText  is " + commonsTrimedText + "<- end here");
        System.out.println("=======================================");


        String charMatcherTrimedText = null;
        long charMatcherStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            charMatcherTrimedText = CharMatcher.is(' ').trimTrailingFrom(sampleText);
        }
        long charMatcherFinished = System.currentTimeMillis();
        System.out.println("quavaStarted       at " + charMatcherStarted);
        System.out.println("quavaFinished      at " + charMatcherFinished);
        System.out.println("quavaPassed time   is " + ((charMatcherFinished - charMatcherStarted)));
        System.out.println("quavaTrimedText    is " + charMatcherTrimedText + "<- end here");
        System.out.println("=======================================");
    }

}

結果

Executing: gradle :run
Arguments: [-PmainClass=speedcheck.SpeedCheck, -c, D:\workspaceForNetBeans\speedcheck\settings.gradle]

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
original text start from here ->"aaaaaa"    <-to end is here
loop times : 25000000
=======================================
staticStarted      at 1448538448194
staticfinished     at 1448538458284
staticPassed time  is 10090
staticTrimedText   is "aaaaaa"<- end here
=======================================
insideStarted      at 1448538458285
insidefinished     at 1448538479871
insidePassed time  is 21586
insideTrimedText   is "aaaaaa"<- end here
=======================================
commonsStarted     at 1448538479871
commonsFinished    at 1448538480760
commonsPassed time is 889
commonsTrimedText  is "aaaaaa"<- end here
=======================================
quavaStarted       at 1448538480760
quavaFinished      at 1448538483150
quavaPassed time   is 2390
quavaTrimedText    is "aaaaaa"<- end here
=======================================

BUILD SUCCESSFUL

Total time: 35.862 secs

どうしてもNIO2が使えないのでムシャクシャしてやった

仕事でですが、どうしてもJava6の環境で、ファイルの更新管理をするライブラリを作らなきゃいけなかったので、Apache Commons IO使って作ってみた。

github.com

参考にしたのはここです。 ありがとうございました。

qiita.com

実際に使うときはもっと修正していますが、サンプルにどうぞ。

ResourceMonitor

package sample.file.monitor.monitor;

import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.log4j.Logger;
import sample.file.monitor.listner.ResourceListner;
import sample.file.monitor.resource.Resource;

/**
 *
 * @author yushi.koga
 */
public class ResourceMonitor {

    static private final ResourceMonitor resourceMonitor = new ResourceMonitor();
    static private final Logger log = Logger.getLogger(ResourceMonitor.class);
    private long refreshDelay = 4000L;
    private URI monitoringDirectory = null;
    static private final String MONITORING_FILE = "target.txt";

    public static ResourceMonitor getInstance() {
        return resourceMonitor;
    }

    public void setRefreshDelay(long refreshDelay) {
        this.refreshDelay = refreshDelay;
    }

    public void setMonitoringDirectory(URI monitoringDirectory) throws URISyntaxException, MalformedURLException {
        this.monitoringDirectory = monitoringDirectory;
    }

    private ResourceMonitor() {
    }

    public void start() throws ConfigurationException, MalformedURLException, Exception {
        // Guard Clause
        if (monitoringDirectory == null) {
            throw new IllegalStateException("Please set monitoring directory");
        }
        // Generate Monitor. Interval is milli seconds.
        FileAlterationMonitor monitor = new FileAlterationMonitor(refreshDelay);

        // Generate Observer.Set monitoring directory.
        final File dir = new File(monitoringDirectory);
        FileFilter filter = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().equals(MONITORING_FILE);
            }
        };
        FileAlterationObserver observer = new FileAlterationObserver(dir, filter);

        // Generate and register Lister to Observer
        ResourceListner listener = new ResourceListner();
        observer.addListener(listener);

        // Register Observer to Monitor
        monitor.addObserver(observer);

        // Start Monitor
        Resource.load(new URL(monitoringDirectory.toURL(), MONITORING_FILE));
        monitor.start();
        log.info(this.getClass().getSimpleName() + " started monitoring");
    }
}

ResourceListner

package sample.file.monitor.listner;

import java.io.File;
import java.net.MalformedURLException;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.log4j.Logger;
import sample.file.monitor.resource.Resource;

/**
 *
 * @author yushi.koga
 */
public class ResourceListner extends FileAlterationListenerAdaptor {

    private final Logger log = Logger.getLogger(ResourceListner.class);

    private void load(File file, String event) {
        try {
            Resource.load(file.toURI().toURL());
        } catch (MalformedURLException ex) {
            log.error(file.getName() + "is " + event + "but failed to load ", ex);
        } catch (ConfigurationException ex) {
            log.error(file.getName() + "is " + event + "but failed to load ", ex);
        }
    }

    @Override
    public void onFileChange(File file) {
        log.info(file.getName() + "is updated");
        load(file, "updated");

    }

    @Override
    public void onFileCreate(File file) {
        log.info(file.getName() + "is created");
        load(file, "created");
    }

    @Override
    public void onFileDelete(File file) {
        log.info(file.getName() + "is deleted");
    }

}
Resource
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package sample.file.monitor.resource;

import java.net.URL;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.log4j.Logger;

/**
 *
 * @author yushi.koga
 */
public class Resource {

    static private final Resource resource = new Resource();
    static private final Logger log = Logger.getLogger(Resource.class);
    static private PropertiesConfiguration configuration;

    private Resource() {
    }

    public static Resource getInstance() {
        return resource;
    }

    public static void load(URL resourceFile) throws ConfigurationException{
        try {
            configuration = new PropertiesConfiguration(resourceFile);
        } catch (ConfigurationException ex) {
            log.error("failde to load " + resourceFile.toString(),ex);
            throw new ConfigurationException(ex);
        }
    }

    public String getString(String key) {
        return configuration.getString(key);
    }

    public int getInt(String key) {
        return configuration.getInt(key);
    }

    public long getLong(String key) {
        return configuration.getLong(key);
    }
}