20200124のJavaに関する記事は10件です。

Javaのequalsは結構気持ち悪い

導入

こんにちは、けちょんです。
Java書いてますか?
そんな私はここ数か月コーディングしてません。
焦ってJava goldの勉強を始めました(今日から)
その際気持ち悪かったことがあったので紹介します。

なにが気持ち悪かったか

皆さん、文字列を比較する際、どう比較しますか?

素人プログラマの場合

String str1 = "test";
String str2 = "test";
if (str1 == str2) {
    System.out.print("same");
} else {
    System.out.print("diff");
}

はい、だめですね。
こんなコード書いたら怒られます。はい、私です。
演算子である"=="はオブジェクトのメモリアドレスを比較します。

異なるオブジェクトは基本的に異なるメモリアドレスを保持します。
今回比較したいのはオブジェクトの持つ文字列であるため、意図に反しています。

が、この場合は"same"が出力されます。
String自体が気持ち悪いため、注意が必要です。
本題ではないし、詳しく知らないため省略します。

一般プログラマの場合

String str1 = "1";
String str2 = "1";
if (str1.equals(str2)) {
    System.out.print("same");
} else {
    System.out.print("diff");
}

普通ですね。よく見ます。
でも、皆さんObject.equalsの中身みたことありますか?

中身は、、、

public boolean equals(Object obj) {
    return (this == obj);
}

"=="を使っとるやーーーん

"=="を使っていて怒られた私は大激怒です。

まとめ

文字列の比較はeqaulsも"=="も一緒????

いやいや

もちろんそんなことはありません。

equalsメソッドは、Stringクラスがオーバーライドしています。
以下がString.equalsの中身です。

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value, aString.value)
                              : StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}

これも"=="は使っているものの、メモリアドレスが異なる場合は別の比較を行っていますね。

まとめ

Object.equalsはただの"=="での比較
String.equalsは"=="で比較するものの、比較対象が文字列だった場合は特殊な動きをする(こちらの期待する動き)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java14でdata class的な機能が追加されるらしい

JavaでフィールドとコンストラクタとGetter(場合によってはSetter,equals,toString)しか持たないクラスを書いたことがある人は多いと思います.そのようなクラスはデータクラスと呼ばれれています.
場合によっては似たようなクラスを大量に書かなければならないといけず,いくらIDEのメソッド生成機能を使っても煩わしいことこの上ないですよね.
lombokを使うと多少は軽減されるが,IDEの設定などをしないといけない場合も多く,面倒であることは変わりません.

そんな中,この問題を解決するような機能がJava14で追加されるらしい.

Java14の新機能

Java14ではRecordなるものが導入されます.
https://blogs.oracle.com/javamagazine/records-come-to-java

使い方は,データクラスにしたいクラスをclassではなくrecordと宣言するらしい.

使用例

使用前

FXOrderClassc.java
public final class FXOrderClassic {
    private final int units;
    private final CurrencyPair pair;
    private final Side side;
    private final double price;
    private final LocalDateTime sentAt;
    private final int ttl;

    public FXOrderClassic(int units, 
               CurrencyPair pair, 
               Side side, 
               double price, 
               LocalDateTime sentAt, 
               int ttl) {
        this.units = units;
        this.pair = pair; // CurrencyPair is a simple enum
        this.side = side; // Side is a simple enum
        this.price = price;
        this.sentAt = sentAt;
        this.ttl = ttl;
    }

    public int units() {
        return units;
    }

    public CurrencyPair pair() {
        return pair;
    }

    public Side side() {
        return side;
    }

    public double price() { return price; }

    public LocalDateTime sentAt() {
        return sentAt;
    }

    public int ttl() {
        return ttl;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) 
            return false;

        FXOrderClassic that = (FXOrderClassic) o;

        if (units != that.units) return false;
        if (Double.compare(that.price, price) != 0) 
            return false;
        if (ttl != that.ttl) return false;
        if (pair != that.pair) return false;
        if (side != that.side) return false;
        return sentAt != null ? 
            sentAt.equals(that.sentAt) : that.sentAt == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = units;
        result = 31 * result + 
                   (pair != null ? pair.hashCode() : 0);
        result = 31 * result + 
                   (side != null ? side.hashCode() : 0);
        temp = Double.doubleToLongBits(price);
        result = 31 * result + 
                   (int) (temp ^ (temp >>> 32));
        result = 31 * result + 
                   (sentAt != null ? sentAt.hashCode() : 0);
        result = 31 * result + ttl;
        return result;
    }

    @Override
    public String toString() {
        return "FXOrderClassic{" +
                "units=" + units +
                ", pair=" + pair +
                ", side=" + side +
                ", price=" + price +
                ", sentAt=" + sentAt +
                ", ttl=" + ttl +
                '}';
    }
}

大量のGetterとコンストラクタ,Overrideメソッドが並んでいて見るだけでうんざりしますね.
これがJava14ではこうなります.

FXOrder.java
public record FXOrder(int units,
                      CurrencyPair pair,
                      Side side,
                      double price,
                      LocalDateTime sentAt,
                      int ttl) {}

Kotlinみたい
とてもすっきりと宣言できるようになることが分かると思います.
ただし,Setterは用意されないようなので,ORマッパーなどを使う際は注意が必要です.

まとめ

もうKotlinで良いと思う
Java14ではRecordという強力な新機能が導入されるらしいです.
これでレガシーなコードも簡潔に書き換えることができそうです.
個人的にもこの機能はぜひ使いたいと思っていて,Java8からアップデートしようと考えています!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SendGrid Javaライブラリの、メール配信失敗時の実装を確認した話

前置き

JavaでWebアプリケーションを作成しています。
システムからメール配信を行うため、SendGridを利用しているのですが、なんらかの原因でメール配信が失敗した場合に、想定していた後処理がコケていたので備忘録として記載します。
すべては前任者の「エラー時にもResponseで結果を返してくるだろう」という思い込みから発生した実装でした。

SendGridを使用してメール配信処理を作成

build.gradleにSendGrid Javaライブラリをプロジェクトにインストールしメール送信メソッドを作成。
SendGridが400番台のエラーを返してくる場合はこちらから送ったリクエストパラメータの不備やリクエスト超過のためリトライは行わず、ログを残してフロント側にエラー情報を返して処理を終了します。
500番台の場合はSendGridサーバにエラーが発生している状態のため、2〜3度リトライを行うように作成していました。
リトライ周りとか省略して(すみません)コードを記載します。

参考:SendGrid:ステータスコードとエラー

dependencies {
    compile ('com.sendgrid:sendgrid-java:4.0.1')
}
import com.sendgrid.*;
import java.io.IOException;

public class Example {
  public static void main(String[] args) throws IOException {
    SendGrid sg = new SendGrid(System.getenv("SENDGRID_API_KEY"));
    try {
      Request request = new Request();
      request.setMethod(Method.GET);
      request.setEndpoint("api_keys");
      Response response = sg.api(request);
      if (response.getStatusCode() < 300) {
            // 200番台は正常送信として処理を続ける
      } else if (response.getStatusCode() < 500){
            // 400番台エラーはリクエストパラメータが不正と判断して処理終了
            // ログを残し、エラーメッセージをフロントへ返却
      } else {
            // 500番台エラーはSendGridのサーバエラーのため何度かリトライ
            // ログは残しておく
      }
    } catch (IOException ex) {
      throw ex;
    }
  }
}

想定された処理が行われない問題

上記のように返ってきたresponse内のステータスコードを判断してその後の処理を振り分けるように作成していました(前任者が)
メール作成時のバリデーションチェック処理に不備があり、メールが送れなかった際に後処理が行われず、想定されたログも残っておらずでこの実装に不備があることが判明します。

実装を確認する

com.sendgrid.SendGridクラスのapi()メソッドからたどって実際にどこでSendGridにリクエスト送信を行っているかを確認します。(3〜4ステップでたどり着けるので中略)

private Response executeApiCall(HttpRequestBase httpPost) throws IOException {
    try {
        CloseableHttpResponse serverResponse = httpClient.execute(httpPost);
        try {
            Response response = getResponse(serverResponse);
            if(response.getStatusCode() >= 300) {
                //throwing IOException here to not break API behavior.
                throw new IOException("Request returned status Code "+response.getStatusCode()+"Body:"+response.getBody());
            }
            return response;
        } finally {
            serverResponse.close();
        }
    } catch(ClientProtocolException e) {
        throw new IOException(e.getMessage());
    }
}

com.sendgrid.ClientクラスにexecuteApiCall()メソッドがあり、この3行目でexecute()していること、そしてなにより6行目のif文で、IOExceptionがthrowされていることがわかります。
ステータスコードが300より大きいときも、responseにその値を詰めて返してくれていいのよ。。。

SebdGridの返却値とその後

Caused by: java.io.IOException: Request returned status Code 400Body:
{
    "errors": [
        {
            "message": "Invalid replyTo email address",
            "field": "reply_to",
            "help": null
        }
    ]
}

SendGridは上記の様にエラーメッセージを返してくれるので、responseではなくcatchしたIOExceptionから後続処理を書くべきでした。
なお、ここ1〜2年SendGridでは大規模障害が発生していなさそうなことと、メールの使用率を鑑みてリトライ処理は消してさくっとエラーメッセージを表示するようにしました(バリデーションチェックは直したので400番台エラーは発生しなくなるはず!)。

雑感

思い込みと諸々のテストの不備が重なったかなしい事件でしたが、使用ライブラリの返却値はしっかり確認して使おうねと学んだ出来事でした。

※あくまでJavaライブラリの実装なので、他言語のライブラリではエラー時にもresponseに詰めて返してくれるのかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DBUnitの空文字列とか日付とか使いづらいので直して使う

1. はじめに

Java+RDBMSの開発プロジェクトで、単体テストツールにDBUnitを使用するケースは未だ(2020年)に多いと思います。
DBUnitはDBの初期データと実行後のアサーションデータをxls形式やCSV形式を利用できることで非常に便利かつ開発省力化に有効なツールですが、Excelのデータ形式やPOIの仕様の問題で若干使いづらい点があります。

具体的には件名で挙げた通り、可変文字列型(VARCHAR型)の空文字列とNULLを分別できない点と、日付型(DATETIME、TIMESTAMP、DATE、TIME等)の値を正確に設定できない問題があります。

本稿では、この対策方法を記述します。
なお、本稿でのDbUnitのバージョンは2.5.4を使用しています。

2. DBUnitはどうなっているのか?

2.1 セル情報の読み込み

DBUnitではXlsTableクラスのgetValue(int row, String column)メソッドで、Excelのセルの値を取得してDBに設定する値に変換しています。

XlsTable#getValueから抜粋
    int type = cell.getCellType();
    switch (type)
    {
        case HSSFCell.CELL_TYPE_NUMERIC:
            if (HSSFDateUtil.isCellDateFormatted(cell))
            {
                return cell.getDateCellValue();
            }
            return new BigDecimal(cell.getNumericCellValue());

        case HSSFCell.CELL_TYPE_STRING:
            return cell.getStringCellValue();

        case HSSFCell.CELL_TYPE_FORMULA:
            throw new DataTypeException("Formula not supported at row=" +
                    row + ", column=" + column);

        case HSSFCell.CELL_TYPE_BLANK:
            return null;

        case HSSFCell.CELL_TYPE_BOOLEAN:
            return cell.getBooleanCellValue() ? Boolean.TRUE : Boolean.FALSE;

        case HSSFCell.CELL_TYPE_ERROR:
            throw new DataTypeException("Error at row=" + row +
                    ", column=" + column);

        default:
            throw new DataTypeException("Unsupported type at row=" + row +
                    ", column=" + column);
    }

上記のコードからわかる通り、セルのデータ型が数値型で特定のフォーマットの場合に日付型としています。また、セルが空の場合にNULLとしています。
このため、DBに設定する日付型の値はExcelの日付データの精度によって差分が表示ます。たとえばExcel上では2020/1/24 10:00と入れたものがDBのDATETIME型のカラムには2020/1/24 10:00:01になることがあります。
また、セルの値が空の場合は一律nullになるため空文字列のデータを作ることができません。

2.2 セル情報への出力

DBUnitには、DB(だけに限らないのですが)から読み取ったデータをExcelファイルに出力する機能も提供しています。
Excelへの出力はXlsDataSetクラスのwrite(IDataSet dataSet, OutputStream out)メソッドで行っています。
writeメソッドの内部でセルに設定する値の取得をしています。

XlsDataSet#writeから抜粋
        // write table data
        for (int j = 0; j < table.getRowCount(); j++)
        {
            HSSFRow row = sheet.createRow(j + 1);
            for (int k = 0; k < columns.length; k++)
            {
                Column column = columns[k];
                Object value = table.getValue(j, column.getColumnName());
                if (value != null)
                {
                    HSSFCell cell = row.createCell((short)k);
                    cell.setEncoding(HSSFCell.ENCODING_UTF_16);
                    cell.setCellValue(DataType.asString(value));
                }
            }
        }

上記のコードでIDataSet内のITableに格納されている値を取得してセルに設定をしています。
値がnullの場合はセルの生成自体をしないため、Excelの表示上は空のセルとなります。

3. どう直すのか

上記で見たDBUnitのソースコードからDBUnitの処理の内容そのものの他に、データ型に対するExcelセル上の値の変換方法を利用者側で変更することができないこともわかります。
このため、DBUnitを都合よく使うためにXlsTableXlsDataSetのソースコードを修正して独自のクラスを作り、そのクラスからDBUnitを使用することにします。

3.1 セル情報の読み込み

XlsTable#getValueメソッドで、セルのデータ型が文字列型の場合でも日付型のパータンにマッチする場合は日付型を設定するように変更します。
また、セル上にnullと文字列を記載した場合はDBにNULL値を設定するようにします。

XlsTable#getValueを修正
    int type = cell.getCellType();
    switch (type)
    {
        case HSSFCell.CELL_TYPE_NUMERIC:
            if (HSSFDateUtil.isCellDateFormatted(cell))
            {
                return cell.getDateCellValue();
            }
            return new BigDecimal(cell.getNumericCellValue());

        case HSSFCell.CELL_TYPE_STRING:
            /* 独自実装(ここから) */
            String cellValue = cell.getRichStringCellValue().getString();

            // セルの値が"null"の場合はNULLを設定
            if ("null".equals(cellValue)) { return null; }

            // セルの値が"yyyy/MM/dd HH:mm:ss"形式の場合はDate型にパースして設定
            if (Pattern.compile("\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2}").matcher(cellValue).matches()) {
                return new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(cellValue);
            }

            // 上記以外はセルの値をそのまま設定
            return cellValue;
            /* 独自実装(ここまで) */

        case HSSFCell.CELL_TYPE_FORMULA:
            throw new DataTypeException("Formula not supported at row=" +
                    row + ", column=" + column);

        case HSSFCell.CELL_TYPE_BLANK:
            // セルが空の場合は空文字列を返却
            return "";

        case HSSFCell.CELL_TYPE_BOOLEAN:
            return cell.getBooleanCellValue() ? Boolean.TRUE : Boolean.FALSE;

        case HSSFCell.CELL_TYPE_ERROR:
            throw new DataTypeException("Error at row=" + row +
                    ", column=" + column);

        default:
            throw new DataTypeException("Unsupported type at row=" + row +
                    ", column=" + column);
    }

3.2 セル情報への出力

JUnitの初期データ設定とDBデータの比較だけであればセル情報の読み込みだけを直せばよいのですが、DBデータのxls形式出力も使用すると試験データの作成を効率化できます。
そのため、セル情報読み込みの修正に合わせて、セル情報への出力処理も修正します。

XlsDataSet#writeを修正
        // write table data
        for (int j = 0; j < table.getRowCount(); j++)
        {
            HSSFRow row = sheet.createRow(j + 1);
            for (int k = 0; k < columns.length; k++)
            {
                Column column = columns[k];
                Object value = table.getValue(j, column.getColumnName());
                /* 独自実装(ここから) */
                if (null == value) {
                    cell.setCellValue("null");
                } else if (value instanceof java.sql.Timestamp) {
                    cell.setCellValue(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(value));
                } else {
                    cell.setCellValue(DataType.asString(value));
                }
                /* 独自実装(ここまで) */
            }
        }

値がnullの場合はセルの値に"null"文字列を設定するようにします。
データ型がTimestamp型の場合はyyyy/MM/dd HH:mm:ss形式の文字列を設定するようにします。

4. 独自クラスの実装

上記の方針で、実際にXlsDataSetXlsTableのソースコードを拝借して独自クラスを定義します。
修正箇所はJDKライブラリのみを使用しているので、DBUnitを使用できる環境であればコンパイル可能だと思います。

XlsDataSetを修正したクラスとしてMyXlsDataSetクラスを定義します。XlsTableクラスはパッケージプライベートクラスとして定義されていて外部パッケージからはアクセスできないクラスとなっています。
ここでは、MyXlsTableクラスをMyXlsDataSetの内部クラスとして定義します。

日付型についてはDATETIME型の他にDATE型やTIME型も考慮して、PatternをキーDateFormatを値とするHashMapを定義して、Mapの各キーのPatternを検査してマッチした場合はキーに紐づく値のDateFormatでパースするようにしています。ここは、MapではなくてもPatternDateFormatの組み合わせを格納しListや配列でも構いません。

このコードはあくまで試験用なので同期化を考慮していないので注意が必要です。
複数のスレッドからこのクラスを使ってExcelファイルを読み取る場合は、SimpleDateFormatを都度作成するかThreadLocalに格納するかの工夫が必要です。

MyXlsDataSet
public class MyXlsDataSet extends AbstractDataSet {
    private static final Logger logger = LoggerFactory.getLogger(MyXlsDataSet.class);

    /* 独自実装(ここから) */
    private static final SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd");
    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
    /* 独自実装(ここまで) */

    private final ITable[] _tables;

    /**
     * Creates a new XlsDataSet object that loads the specified Excel document.
     */
    public DateFormattedXlsDataSet(File file) throws IOException, DataSetException {
        this(new FileInputStream(file));
    }

    /**
     * Creates a new XlsDataSet object that loads the specified Excel document.
     */
    public DateFormattedXlsDataSet(InputStream in) throws IOException, DataSetException {
        HSSFWorkbook workbook = new HSSFWorkbook(in);
        _tables = new ITable[workbook.getNumberOfSheets()];
        for (int i = 0; i < _tables.length; i++) {
            _tables[i] = new DateFormattedXlsTable(workbook.getSheetName(i), workbook.getSheetAt(i));
        }
    }

    /**
     * Write the specified dataset to the specified Excel document.
     */
    public static void write(IDataSet dataSet, OutputStream out)
            throws IOException, DataSetException {
        logger.debug("write(dataSet=" + dataSet + ", out=" + out + ") - start");

        HSSFWorkbook workbook = new HSSFWorkbook();

        int index = 0;
        ITableIterator iterator = dataSet.iterator();
        while (iterator.next()) {
            // create the table i.e. sheet
            ITable table = iterator.getTable();
            ITableMetaData metaData = table.getTableMetaData();
            HSSFSheet sheet = workbook.createSheet(metaData.getTableName());

            // write table metadata i.e. first row in sheet
            // workbook.setSheetName(index, metaData.getTableName(), HSSFWorkbook.ENCODING_UTF_16);
            workbook.setSheetName(index, metaData.getTableName());

            HSSFRow headerRow = sheet.createRow(0);
            Column[] columns = metaData.getColumns();
            for (int j = 0; j < columns.length; j++) {
                Column column = columns[j];
                HSSFCell cell = headerRow.createCell((short) j);
                // cell.setEncoding(HSSFCell.ENCODING_UTF_16);
                cell.setCellValue(column.getColumnName());
            }

            // write table data
            for (int j = 0; j < table.getRowCount(); j++) {
                HSSFRow row = sheet.createRow(j + 1);
                for (int k = 0; k < columns.length; k++) {
                    Column column = columns[k];
                    Object value = table.getValue(j, column.getColumnName());

                    /* 独自実装(ここから) */
                    HSSFCell cell = row.createCell((short) k);
                    if (null == value) {
                        cell.setCellValue("null");
                    } else if (value instanceof java.sql.Timestamp) {
                        cell.setCellValue(TIMESTAMP_FORMAT.format(value));
                    } else if (value instanceof java.sql.Date) {
                        cell.setCellValue(DATE_FORMAT.format(value));
                    } else if (value instanceof Time) {
                        cell.setCellValue(TIME_FORMAT.format(value));
                    } else {
                        cell.setCellValue(DataType.asString(value));
                    }
                    /* 独自実装(ここまで) */

                    // if (value != null) {
                    //   HSSFCell cell = row.createCell((short) k);
                    //   cell.setEncoding(HSSFCell.ENCODING_UTF_16);
                    //   cell.setCellValue(DataType.asString(value));
                    // }
                }
            }

            index++;
        }

        // write xls document
        workbook.write(out);
        out.flush();
    }

    ////////////////////////////////////////////////////////////////////////////
    // AbstractDataSet class

    protected ITableIterator createIterator(boolean reversed) throws DataSetException {
        // logger.debug("createIterator(reversed=" + reversed + ") - start");

        return new DefaultTableIterator(_tables, reversed);
    }

    private static class MyXlsTable extends AbstractTable {
        /* 独自実装(ここから) */
        // UT用なので同期化は考慮しない
        private static final HashMap<Pattern, SimpleDateFormat> DATETIME_PATTERN_MAP =
                new HashMap<Pattern, SimpleDateFormat>();

        static {
            DATETIME_PATTERN_MAP.put(
                    Pattern.compile("\\d{4}/\\d{2}/\\d{2}"), new SimpleDateFormat("yyyy/MM/dd"));
            DATETIME_PATTERN_MAP.put(
                    Pattern.compile("\\d{4}-\\d{2}-\\d{2}"), new SimpleDateFormat("yyyy-MM-dd"));
            DATETIME_PATTERN_MAP.put(
                    Pattern.compile("\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2}"),
                    new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"));
            DATETIME_PATTERN_MAP.put(
                    Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"),
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        }
        /* 独自実装(ここまで) */
        // private static final Logger logger = LoggerFactory.getLogger(XlsTable.class);

        private final ITableMetaData _metaData;
        private final Sheet _sheet;

        private final DecimalFormatSymbols symbols = new DecimalFormatSymbols();

        public DateFormattedXlsTable(String sheetName, Sheet sheet) throws DataSetException {
            int rowCount = sheet.getLastRowNum();
            if (rowCount >= 0 && sheet.getRow(0) != null) {
                _metaData = createMetaData(sheetName, sheet.getRow(0));
            } else {
                _metaData = new DefaultTableMetaData(sheetName, new Column[0]);
            }

            _sheet = sheet;

            // Needed for later "BigDecimal"/"Number" conversion
            symbols.setDecimalSeparator('.');
        }

        static ITableMetaData createMetaData(String tableName, Row sampleRow) {
            logger.debug("createMetaData(tableName={}, sampleRow={}) - start", tableName, sampleRow);

            List columnList = new ArrayList();
            for (int i = 0; ; i++) {
                Cell cell = sampleRow.getCell(i);
                if (cell == null) {
                    break;
                }

                String columnName = cell.getRichStringCellValue().getString();
                if (columnName != null) {
                    columnName = columnName.trim();
                }

                // Bugfix for issue ID 2818981 - if a cell has a formatting but no name also ignore it
                if (columnName.length() <= 0) {
                    // logger.debug("The column name of column # {} is empty - will skip here assuming the
                    // last column was reached", String.valueOf(i));
                    break;
                }

                Column column = new Column(columnName, DataType.UNKNOWN);
                columnList.add(column);
            }
            Column[] columns = (Column[]) columnList.toArray(new Column[0]);
            return new DefaultTableMetaData(tableName, columns);
        }

        ////////////////////////////////////////////////////////////////////////////
        // ITable interface

        public int getRowCount() {
            logger.debug("getRowCount() - start");

            return _sheet.getLastRowNum();
        }

        public ITableMetaData getTableMetaData() {
            logger.debug("getTableMetaData() - start");

            return _metaData;
        }

        public Object getValue(int row, String column) throws DataSetException {
            if (logger.isDebugEnabled()) {
                logger.debug("getValue(row={}, columnName={}) - start", Integer.toString(row), column);
            }

            assertValidRowIndex(row);

            int columnIndex = getColumnIndex(column);
            Cell cell = _sheet.getRow(row + 1).getCell(columnIndex);
            if (cell == null) {
                return null;
            }

            int type = cell.getCellType();
            switch (type) {
                case Cell.CELL_TYPE_NUMERIC:
                    CellStyle style = cell.getCellStyle();
                    if (DateUtil.isCellDateFormatted(cell)) {
                        return getDateValue(cell);
                    } else if (XlsDataSetWriter.DATE_FORMAT_AS_NUMBER_DBUNIT.equals(
                            style.getDataFormatString())) {
                        // The special dbunit date format
                        return getDateValueFromJavaNumber(cell);
                    } else {
                        return getNumericValue(cell);
                    }

                case Cell.CELL_TYPE_STRING:
                    /* 独自実装(ここから) */
                    String cellValue = cell.getRichStringCellValue().getString();
                    if ("null".equals(cellValue)) {
                        return null;
                    }
                    Set<Pattern> patternSet = DATETIME_PATTERN_MAP.keySet();
                    for (Pattern pattern : patternSet) {
                        if (pattern.matcher(cellValue).matches()) {
                            SimpleDateFormat format = DATETIME_PATTERN_MAP.get(pattern);
                            try {
                                return format.parse(cellValue);
                            } catch (ParseException e) {
                                continue;
                            }
                        }
                    }
                    return cellValue;
                  /* 独自実装(ここまで) */

                case Cell.CELL_TYPE_FORMULA:
                    throw new DataTypeException("Formula not supported at row=" + row + ", column=" + column);

                case Cell.CELL_TYPE_BLANK:
                    return ""; // 独自実装

                case Cell.CELL_TYPE_BOOLEAN:
                    return cell.getBooleanCellValue() ? Boolean.TRUE : Boolean.FALSE;

                case Cell.CELL_TYPE_ERROR:
                    throw new DataTypeException("Error at row=" + row + ", column=" + column);

                default:
                    throw new DataTypeException("Unsupported type at row=" + row + ", column=" + column);
            }
        }

        protected Object getDateValueFromJavaNumber(Cell cell) {
            logger.debug("getDateValueFromJavaNumber(cell={}) - start", cell);

            double numericValue = cell.getNumericCellValue();
            BigDecimal numericValueBd = new BigDecimal(String.valueOf(numericValue));
            numericValueBd = stripTrailingZeros(numericValueBd);
            return new Long(numericValueBd.longValue());
        }

        protected Object getDateValue(Cell cell) {
            logger.debug("getDateValue(cell={}) - start", cell);

            double numericValue = cell.getNumericCellValue();
            Date date = DateUtil.getJavaDate(numericValue);
            return new Long(date.getTime());
        }

        /**
         * Removes all trailing zeros from the end of the given BigDecimal value up to the decimal
         * point.
         *
         * @param value The value to be stripped
         * @return The value without trailing zeros
         */
        private BigDecimal stripTrailingZeros(BigDecimal value) {
            if (value.scale() <= 0) {
                return value;
            }

            String valueAsString = String.valueOf(value);
            int idx = valueAsString.indexOf(".");
            if (idx == -1) {
                return value;
            }

            for (int i = valueAsString.length() - 1; i > idx; i--) {
                if (valueAsString.charAt(i) == '0') {
                    valueAsString = valueAsString.substring(0, i);
                } else if (valueAsString.charAt(i) == '.') {
                    valueAsString = valueAsString.substring(0, i);
                    // Stop when decimal point is reached
                    break;
                } else {
                    break;
                }
            }
            BigDecimal result = new BigDecimal(valueAsString);
            return result;
        }

        protected BigDecimal getNumericValue(Cell cell) {
            logger.debug("getNumericValue(cell={}) - start", cell);

            String formatString = cell.getCellStyle().getDataFormatString();
            String resultString = null;
            double cellValue = cell.getNumericCellValue();

            if ((formatString != null)) {
                if (!formatString.equals("General") && !formatString.equals("@")) {
                    logger.debug("formatString={}", formatString);
                    DecimalFormat nf = new DecimalFormat(formatString, symbols);
                    resultString = nf.format(cellValue);
                }
            }

            BigDecimal result;
            if (resultString != null) {
                try {
                    result = new BigDecimal(resultString);
                } catch (NumberFormatException e) {
                    logger.debug("Exception occurred while trying create a BigDecimal. value={}",
                            resultString);
                    // Probably was not a BigDecimal format retrieved from the excel. Some
                    // date formats are not yet recognized by HSSF as DateFormats so that
                    // we could get here.
                    result = toBigDecimal(cellValue);
                }
            } else {
                result = toBigDecimal(cellValue);
            }
            return result;
        }

        /**
         * @param cellValue
         * @return
         * @since 2.4.6
         */
        private BigDecimal toBigDecimal(double cellValue) {
            String resultString = String.valueOf(cellValue);
            // To ensure that intergral numbers do not have decimal point and trailing zero
            // (to restore backward compatibility and provide a string representation consistent with
            // Excel)
            if (resultString.endsWith(".0")) {
                resultString = resultString.substring(0, resultString.length() - 2);
            }
            BigDecimal result = new BigDecimal(resultString);
            return result;
        }

        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(getClass().getName()).append("[");
            sb.append("_metaData=").append(this._metaData == null ? "null" : this._metaData.toString());
            sb.append(", _sheet=").append(this._sheet == null ? "null" : "" + this._sheet);
            sb.append(", symbols=").append(this.symbols == null ? "null" : "" + this.symbols);
            sb.append("]");
            return sb.toString();
        }
    }
}

引用したソースコードが2020年1月24日現在で最新の2.6.0になっていましたが、検証に使用したDBUnitのバージョンは2.5.4であるため、Excelのエンコードに関する設定をコメントアウトしています。
2.6.0以上を使用する場合は下記の行のコメントを解除してください。

            // workbook.setSheetName(index, metaData.getTableName(), HSSFWorkbook.ENCODING_UTF_16);
                // cell.setEncoding(HSSFCell.ENCODING_UTF_16);

5. 独自クラスを使う

上記の独自クラスのコンパイルが成功したら、使い方はXlsDataSetと全く同じです。
ただし、DBUnitは標準で空白のセルの出力を許していないため、取得したDatabaseConnectionに対してDatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDSをtrueにする処理を入れた方が良いです。

ExcelデータをDBに設定
    DatabaseConnection connection = new DatabaseConnection(sqlConnection, schemaName);
    connection.getConfig().setProperty(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS, true);
    IDataSet xlsDataSet = new MyXlsDataset(new File(xlsFilePath));
    DatabaseOperation.CLEAN_INSERT.execute(connection, compositDataSet);
ExcelデータとDBを比較
    DatabaseConnection connection = new DatabaseConnection(sqlConnection, schemaName);
    connection.getConfig().setProperty(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS, true);
    IDataSet expected= new MyXlsDataset(new File(expectedXlsFilePath));
    QueryDataSet actual = new QueryDataSet(connection);
    Assert.assertEquals(expected, actual);
DBデータをExcelファイルに出力
    DatabaseConnection connection = new DatabaseConnection(sqlConnection, schemaName);
    connection.getConfig().setProperty(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS, true);
    QueryDataSet dbDataSet= new QueryDataSet(connection);
    FileOutputStream fileOutputStream = new FileOutputStream(outputFilePath);
    MyXlsDataset.write(dbDataSet, fileOutputStream);

gradleを使用しているプロジェクトでは、gradleタスクとしてDBUnitを使ってDBのエクスポートやインポートをしたくなるかもしれません。もちろんそれは可能ですが、その場合は上記のMyXlsDataSetクラスはjarライブラリにする必要があります。

6. さいごに

DBUnitはLGPLライセンスを持っています。
作成したプログラムのテストコードを含めて配布する場合など上記のコードを実装コードに含める場合は、LGPLライセンスに従う必要があるので注意をしてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

libGDXでandroidアプリのチューニングをした話

はじめに

もう9年前。iOSアプリが日本ランキングの1位になった。
iOSソリティアV
さりげなく自慢:relaxed:

調子に乗ってandroidアプリも作った。nativeで作った。
nativeで当時android2.xなので、SurfaceViewを使った。
androidソリティアV

アニメがいまいちだったけど:sob:、ちゃんとできた。
フレームレートがちょっと低かった。

android/iOS共に現在10周年記念中。

数年前、チューニングしたいな、と思った。
iOSとソース一本化
したいけど、一本化するにはやることが多すぎる・・・。

うーん、GLSerfaceViewとかか。めんどくさそうだぞ。しかもよくなるか保証がない。

そんなとき、こんな記事が
Android StudioでlibGDX入門

ああlibGDX・・・web化しようとして、前にやったなあ。GWTがいまいちで途中で止めたんだった。
一部の画面のみlibGDXとか、できるのか。

そんなわけで一部の機能のみlibGDX、問題ない機能はそのままnative。な構成のandroidアプリとしてチューニングしてみることになったのであった。

結果

良かった点

  • ちゃんとチューニングになった。開発も結構サクサクいけた。
  • 前に捨てたlibGDXコードもちゃんと再利用できた。
  • Javaソース追えるので、いろいろ助かった。
  • android nativeのレイアウトをlibGDX画面の上に出したりもできた。ほんとに最低限のみ、libGDX化で行けた。楽だった。
  • 使い慣れたテクスチャパッカーも使える。

悪かった点

  • 日本語の突っ込んだ情報が無い・・・

最後に

結構良かった。androidアプリとして、間違った選択ではなかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java備忘 LocalDateを使ってみた

はじめに

Javaで○日以上古いファイルを検出するというプログラムを書く機会があったので、備忘として残しておく。
あくまで一つの例として。

LocalDateを使う

Dateでも可能だが、より新しいLocalDateを使ってみる。

Long lastModified = targetFile.lastModified();
    LocalDate lastDate = Instant.ofEpochMilli(lastModified).atZone(ZoneId.systemDefault()).toLocalDate();

これでファイルクラスで宣言した対象のファイルから最終更新日を取得する。
エポック時間をLocalDate型に変換しているイメージ。

LocalDate daysBefore = LocalDate.now().minusDays(5); 

LocalDateクラスのnow()で本日の日付を取得する。
今回は5日前の日付を対象にするとして、minusDays(5)で取得することができる。

if (lastModified.isBefore(base) || lastModified.isEqual(base))

if (lastModified.compareTo(base) <= 0)

if (lastModified.until(LocalDate.now(), ChronoUnit.DAYS) >= 5)

あとはこんな感じのif文を書いてあげれば指定した日付以前のファイルを炙り出せるはず。
注意したいのはisBefore()だけでは5日より前しか取れないこと。
isEqual()で5日前も取れるようにしてあげる。

追記
@swordoneさんからコメントをいただき、
if文の条件式を追加しました。

まとめ

ここにFileクラスをピピーっとぶっ込んでやるといい感じなる。
ご指摘等ございましたらコメントお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Eclipse JavaからSQLファイルに飛ぶ(ジャンプ)方法

Eclipse java DomaSQLでJavaからSQLファイルに飛ぶ方法

SQLファイルとリンクしている関数を右クリック > Doma > Jump To Sqlファイル

image.png

メニューに出ない場合はDomaToolがインストールされていない可能性あり。
以下に手順を示す。

DomaToolのインストール

  1. ヘルプ > 新規ソフトウェアのインストール
  2. http://eclipse.seasar.org/updates/3.5/ > 追加
  3. Domaにチェックし、次へ
  4. インストールを完了する image.png

参考:

Doma - セットアップ
http://doma.seasar.org/setup.html#Eclipse%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Eclipse JavaからSQLファイルに飛ぶ方法

Eclipse java DomaSQLでJavaからSQLファイルに飛ぶ方法

SQLファイルとリンクしている関数を右クリック > Doma > Jump To Sqlファイル

image.png

メニューに出ない場合はDomaToolがインストールされていない可能性あり。
以下に手順を示す。

DomaToolのインストール

  1. ヘルプ > 新規ソフトウェアのインストール
  2. http://eclipse.seasar.org/updates/3.5/ > 追加
  3. Domaにチェックし、次へ
  4. インストールを完了する image.png

参考:

Doma - セットアップ
http://doma.seasar.org/setup.html#Eclipse%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure EventhubsにSASトークンを使う方法(Java)

はじめに

こんにちは、とがりと申します。
SREをしています。

なぜ書いたか

Azure Eventhubsを使っていて調べても全然ドキュメント出てこなく、詰まったから。
本記事はJavaの環境が既に用意されている人を対象にしております。(maven, gradle, etc.)

本題

やりたいこと

アンドロイドアプリからAzure Eventhubsにデータを送信

Shared Access Signature 用いてEventhubsにアクセスしたい。1
しかし、SASを持っていればEventhubsに対して半永久的にアクセスすることができてしまうため
クライアントサイドに配置するのはセキュリティの観点で不安である。
何かいい方法がないかと探していたところSasからTokenを生成できるという情報を得た。
なのでSasTokenを用いてEventhubsにデータを送信する。
詰まった部分もあったので備忘録として記す。

やってみる

  1. SasTokenを作成する
  2. SasTokenを用いてEventhubsにメッセージを送信する
  3. ちゃんとメッセージが届いているかチェックする
1. SasTokenを作成する

Azure ポータル
→Eventhubs 名前空間
→Eventhubs インスタンス
→共有アクセスポリシー
→共有アクセスポリシー名
→主キー&接続文字列–主キー
を使います。
接続文字列–主キーの
Endpoint=sb://(Eventhubs名).servicebus.windows.net/;SharedAccessKeyName=.......;EntityPath=(エントリーポイント)
これを変形して以下の[RESOURCEURI]に用います

スクリーンショット 2020-01-24 9.55.05.png

使う変数
[RESOURCEURI]=sb://(Eventhubs名).servicebus.windows.net/(エントリーポイント)
[KEYNAME]=共有アクセスポリシー名
[KEY]=共有アクセスポリシー名の主キー
SASToken.java
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SASToken
{
    public static void main(String[] args) {
        long epoch = System.currentTimeMillis() / 1000L;
        int week = 60 * 60 * 24 * 7;
        String resourceUri ="[RESOURCEURI]";
        String keyName = "[KEYNAME]";
        String key = "[KEY]";
        String expiry = Long.toString(epoch + week);
        String sasToken = null;
        try {
            String stringToSign = URLEncoder.encode(resourceUri, "UTF-8") + "\n" + expiry;
            String signature = getHMAC256(key, stringToSign);
            sasToken = "SharedAccessSignature sr=" + URLEncoder.encode(resourceUri, "UTF-8") + "&sig=" +
                    URLEncoder.encode(signature, "UTF-8") + "&se=" + expiry + "&skn=" + keyName;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println(sasToken);
    }

    // hash値を返す関数
    public static String getHMAC256 (String key, String input){
        Mac sha256_HMAC = null;
        String hash = null;
        try {
            sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            Base64.Encoder encoder = Base64.getEncoder();
            hash = new String(encoder.encode(sha256_HMAC.doFinal(input.getBytes("UTF-8"))));

        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return hash;
    }
}

これを実行するとsasTokenが得られます。

2. SasTokenを用いてEventhubsにメッセージを送信する
使う変数
[1で得たSasToken]=上記の結果
[NAMESPACENAME]=イベントハブの名前空間
[EVENTHUBNAME]=イベントハブインスタンスの名前
SendToEventhubs.java
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.microsoft.azure.eventhubs.ConnectionStringBuilder;

import com.microsoft.azure.eventhubs.EventData;
import com.microsoft.azure.eventhubs.EventHubClient;
import com.microsoft.azure.eventhubs.EventHubException;

import java.io.IOException;
import java.nio.charset.Charset;
import java.time.Instant;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;


public class SendToEventhubs {
    public static void main(String[] args)
            throws EventHubException, ExecutionException, InterruptedException, IOException {

        String sas = "[1で得たSasToken]";
// 認証情報をセット
        final ConnectionStringBuilder connStr = new ConnectionStringBuilder()
                .setNamespaceName("[NAMESPACENAME]")
                .setEventHubName("[EVENTHUBNAME]")
                .setSharedAccessSignature(sas);
        final Gson gson = new GsonBuilder().create();
        final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
// イベントハブクライアントを記述
        final EventHubClient ehClient = EventHubClient.createSync(connStr.toString(), executorService);

        try {
            for (int i = 0; i < 1; i++) {

                String payload = "Hello, Eventhubs !! (ここにメッセージいれる)";
                byte[] payloadBytes = gson.toJson(payload).getBytes(Charset.defaultCharset());

                EventData sendEvent = EventData.create(payloadBytes);
                System.out.println(sendEvent);
// イベントハブに送信
                ehClient.sendSync(sendEvent);
            }
            System.out.println(Instant.now() + ": Send Complete...");
            System.out.println("Press Enter to stop.");
            System.in.read();
        } finally {
            ehClient.closeSync();
            executorService.shutdown();
        }
    }
}

これでイベントの送信が完了しました。ただこれでは確認できないので受信する方法も乗せておきます。

3. ちゃんとメッセージが届いているかチェックする
使う変数
        String consumerGroupName = "$Default";
        String namespaceName = "[NAMESPACENAME]";
        String eventHubName = "[EVENTHUBNAME]";
        String sasKeyName = "[KEYNAME]";
        String sasKey = "[KEY]";
        String storageConnectionString = "[STORAGECONNECTION]"; //ポータル→ストレージアカウント→アクセスキー
        String storageContainerName = "[STORAGECONTAINERNAME]"; //ポータル→ストレージアカウント→コンテナー
        String hostNamePrefix = "";
EventProcessorSample.java
import com.microsoft.azure.eventhubs.ConnectionStringBuilder;
import com.microsoft.azure.eventhubs.EventData;
import com.microsoft.azure.eventprocessorhost.CloseReason;
import com.microsoft.azure.eventprocessorhost.EventProcessorHost;
import com.microsoft.azure.eventprocessorhost.EventProcessorOptions;
import com.microsoft.azure.eventprocessorhost.ExceptionReceivedEventArgs;
import com.microsoft.azure.eventprocessorhost.IEventProcessor;
import com.microsoft.azure.eventprocessorhost.PartitionContext;

import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;

public class EventProcessorSample
{
    public static void main(String args[]) throws InterruptedException, ExecutionException
    {
        String consumerGroupName = "$Default";
        String namespaceName = "[NAMESPACENAME]";
        String eventHubName = "[EVENTHUBNAME]";
        String sasKeyName = "[KEYNAME]";
        String sasKey = "[KEY]";
        String storageConnectionString = "[STORAGECONNECTION]";
        String storageContainerName = "[STORAGECONTAINERNAME]";
        String hostNamePrefix = "";


        ConnectionStringBuilder eventHubConnectionString = new ConnectionStringBuilder()
                .setNamespaceName(namespaceName)
                .setEventHubName(eventHubName)
                .setSasKeyName(sasKeyName)
                .setSasKey(sasKey);

        EventProcessorHost host = new EventProcessorHost(
                EventProcessorHost.createHostName(hostNamePrefix),
                eventHubName,
                consumerGroupName,
                eventHubConnectionString.toString(),
                storageConnectionString,
                storageContainerName);

        System.out.println("Registering host named " + host.getHostName());
        EventProcessorOptions options = new EventProcessorOptions();
        options.setExceptionNotification(new ErrorNotificationHandler());

        host.registerEventProcessor(EventProcessor.class, options)
                .whenComplete((unused, e) ->
                {
                    if (e != null)
                    {
                        System.out.println("Failure while registering: " + e.toString());
                        if (e.getCause() != null)
                        {
                            System.out.println("Inner exception: " + e.getCause().toString());
                        }
                    }
                })
                .thenAccept((unused) ->
                {
                    System.out.println("Press enter to stop.");
                    try
                    {
                        System.in.read();
                    }
                    catch (Exception e)
                    {
                        System.out.println("Keyboard read failed: " + e.toString());
                    }
                })
                .thenCompose((unused) ->
                {
                    return host.unregisterEventProcessor();
                })
                .exceptionally((e) ->
                {
                    System.out.println("Failure while unregistering: " + e.toString());
                    if (e.getCause() != null)
                    {
                        System.out.println("Inner exception: " + e.getCause().toString());
                    }
                    return null;
                })
                .get(); // Wait for everything to finish before exiting main!

        System.out.println("End of sample");
    }

    // The general notification handler is an object that derives from Consumer<> and takes an ExceptionReceivedEventArgs object
    // as an argument. The argument provides the details of the error: the exception that occurred and the action (what EventProcessorHost
    // was doing) during which the error occurred. The complete list of actions can be found in EventProcessorHostActionStrings.
    public static class ErrorNotificationHandler implements Consumer<ExceptionReceivedEventArgs>
    {
        @Override
        public void accept(ExceptionReceivedEventArgs t)
        {
            System.out.println("SAMPLE: Host " + t.getHostname() + " received general error notification during " + t.getAction() + ": " + t.getException().toString());
        }
    }

    public static class EventProcessor implements IEventProcessor
    {
        private int checkpointBatchingCount = 0;

        // OnOpen is called when a new event processor instance is created by the host.
        @Override
        public void onOpen(PartitionContext context) throws Exception
        {
            System.out.println("SAMPLE: Partition " + context.getPartitionId() + " is opening");
        }

        // OnClose is called when an event processor instance is being shut down.
        @Override
        public void onClose(PartitionContext context, CloseReason reason) throws Exception
        {
            System.out.println("SAMPLE: Partition " + context.getPartitionId() + " is closing for reason " + reason.toString());
        }

        // onError is called when an error occurs in EventProcessorHost code that is tied to this partition, such as a receiver failure.
        @Override
        public void onError(PartitionContext context, Throwable error)
        {
            System.out.println("SAMPLE: Partition " + context.getPartitionId() + " onError: " + error.toString());
        }

        // onEvents is called when events are received on this partition of the Event Hub.
        @Override
        public void onEvents(PartitionContext context, Iterable<EventData> events) throws Exception
        {
            System.out.println("SAMPLE: Partition " + context.getPartitionId() + " got event batch");
            int eventCount = 0;
            for (EventData data : events)
            {
                try
                {
                    System.out.println("SAMPLE (" + context.getPartitionId() + "," + data.getSystemProperties().getOffset() + "," +
                            data.getSystemProperties().getSequenceNumber() + "): " + new String(data.getBytes(), "UTF8"));
                    eventCount++;

                    // Checkpointing persists the current position in the event stream for this partition and means that the next
                    // time any host opens an event processor on this event hub+consumer group+partition combination, it will start
                    // receiving at the event after this one.
                    this.checkpointBatchingCount++;
                    if ((checkpointBatchingCount % 5) == 0)
                    {
                        System.out.println("SAMPLE: Partition " + context.getPartitionId() + " checkpointing at " +
                                data.getSystemProperties().getOffset() + "," + data.getSystemProperties().getSequenceNumber());
                        // Checkpoints are created asynchronously. It is important to wait for the result of checkpointing
                        // before exiting onEvents or before creating the next checkpoint, to detect errors and to ensure proper ordering.
                        context.checkpoint(data).get();
                    }
                }
                catch (Exception e)
                {
                    System.out.println("Processing failed for an event: " + e.toString());
                }
            }
            System.out.println("SAMPLE: Partition " + context.getPartitionId() + " batch size was " + eventCount + " for host " + context.getOwner());
        }
    }
}

こちらのコードを実行することでEventhubs に送信されたデータをリアルタイムに受信することができます。

私が詰まったところ

  • SASTokenを作成するところでResourceUriが何を指しているかわからなかった。
  • SasTokenがどこからどこまでかわからなかった。srからなのか、=の後からなのか全体なのか。 (SharedAccessSignature sr=...)
  • メッセージを送信するときにSasトークンと他になんの情報が必要なのか。

これらをそれぞれ通りの組み合わせでトライエラーをする必要があったので時間と労力がかかった。
この記事を読んでくださったみなさんの何らかの助けとなれば嬉しいです。

参考文献

Java を使用して Azure Event Hubs との間でイベントを送受信する
Shared Access Signature (SAS) を使用して Event Hubs リソースへのアクセスを認証する

最後に

最後まで読んでいただきありがとうございます!
疑問点などございましたら、コメントお待ちしております。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アルゴリズム 体操18

Reverse k Elements

単一リンクリストと整数「k」が引数として渡されます。リストのk要素ずつ反転させるアルゴリズム体操。
k <= 1の場合、リストは変更されません。k >= n(nはリンクリストの長さ)の場合、リンクリスト全体を逆にします。

以下は、k = 3で、3要素ごとに反転した例です。
Screen Shot 2020-01-23 at 9.51.00.png
以下は、k = 4で、4要素ごとに反転した例です。
Screen Shot 2020-01-23 at 9.51.40.png

Solution

比較的に、単純な問題ですが、コード自体は、いくつかのポインターで追跡する必要があるため、少し複雑です。
このアルゴリズムにおいてのポイントは『2つの反転リストを使う』ことです。
一つ目は最終的に返り値として返す、全体反転リスト。
二つ目が全体反転リストに繋げていく、部分(k要素)反転リスト。

  1. reversed:全体反転リストの先頭を指すポインタ。返り値になります。
  2. current_head:サイズ「k」の部分反転リストの先頭。
  3. current_tail:サイズ 'k'の部分反転リストの末尾。
  4. prev_tail:既に処理された全体反転リストの末尾。

例えば、k = 5で、以下のリストがあるとします。
Screen Shot 2020-01-23 at 10.01.14.png

k要素ずつの部分反転リストを作って、全体反転リストに繋げていく感じになります。
Screen Shot 2020-01-23 at 10.02.12.png
Screen Shot 2020-01-23 at 10.06.26.png

実装

ReverseK.java
class ReverseK{
  LinkedListNode reverse_k_nodes(LinkedListNode head, int k) {

    if (k <= 1 || head == null) {
      return head;
    }

    LinkedListNode reversed = null;
    LinkedListNode prev_tail = null;

    while (head != null & k > 0) {

      LinkedListNode current_head = null;
      LinkedListNode current_tail = head;

      int n = k;

      while (head != null && n > 0) {
        LinkedListNode temp = head.next;
        head.next = current_head;
        current_head = head;
        head = temp;
        n--;
      }

      if (reversed == null) {
        reversed = current_head;
      }

      if (prev_tail != null) {
        prev_tail.next = current_head;
      }
      prev_tail = current_tail;
    }

    return reversed;
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む