👏

SpringでのCifs実装

2022/09/27に公開

本記事を書くきっかけ

「Cifs通信をJavaでやりたいんだけど」と言われので、調べたらSpringにもCifsの仕組みがあったので、
自宅で調査した内容に限り纏めて記事にしてみました。

Spring Integration SMB

Spring Integration(Spring 外部機器接続の統合プロジェクト)にて管理されているSMB通信(Cifs)用のライブラリ。
内部でjcifsというライブラリをラップしています。
jcifsを使用されたことがある方は、おおむね使用方法はjcifsと同様になる認識で問題ありません。

実装方法

公式では下記のような実装例が紹介されており、この実装ではSmbSessionFactoryをDIして使用するようになっています。

@Bean
public SmbSessionFactory smbSessionFactory() {
    SmbSessionFactory smbSession = new SmbSessionFactory();
    smbSession.setHost("myHost");
    smbSession.setPort(445);
    smbSession.setDomain("myDomain");
    smbSession.setUsername("myUser");
    smbSession.setPassword("myPassword");
    smbSession.setShareAndDir("myShareAndDir");
    smbSession.setSmbMinVersion(DialectVersion.SMB210);
    smbSession.setSmbMaxVersion(DialectVersion.SMB311);
    return smbSession;
}

基本的にはSmbSessionFactory→SmbSession→SmbFile生成して、SmbFileを操作する形で実装する必要があり、注意するべき点としてはSMBファイルオブジェクトを作成するのに都度Path情報が必要なため誤ったPath指定を行わないようにしなければなりません。

下記のようなURIを作成するようなイメージで設定していくような形で各Setterを呼び出してください
smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?[param=value[param2=value2[...]]]

自分の実装としての要件は都度接続先を変更する必要があったので下記のようなクラスを作成して
接続先ごとにインスタンスを作成して振り分けるように実装してみたのが以下になります。

public class CifsSnd {

	private final SmbSessionFactory smbSessionFactory;

	public CifsSnd(String host, String userName, String password)
			throws CIFSException {
		SmbSessionFactory smbSession = new SmbSessionFactory();
		smbSession.setHost(host);
		smbSession.setPort(445);
		smbSession.setUsername(userName);
		smbSession.setPassword(password);
		smbSession.setSmbMinVersion(DialectVersion.SMB1);
		smbSession.setSmbMaxVersion(DialectVersion.SMB311);

		this.smbSessionFactory = smbSession;
	}

	public boolean fileSend(byte[] fileData, String fileName, String... pathArray) {
		Iterator<String> pathiterator = getPathList(pathArray).iterator();
		String firstPath = pathiterator.next();
        this.smbSessionFactory.setShareAndDir(firstPath);
        try {
            SmbSession smbSession = this.smbSessionFactory.getSession();
            StringBuilder sb = new StringBuilder();
            while (pathiterator.hasNext()) {
                sb.append(pathiterator.next() + "/");
            }
            if (smbSession.exists(sb.toString())) {
                try (SmbFile smbFile = smbSession.createSmbFileObject(sb + fileName)) {
                    smbFile.createNewFile();
                    try(OutputStream os = smbFile.openOutputStream()) {
                        os.write(fileData);
                    }
                }
            } else {
                return false;
            }
        } catch (IOException | IllegalStateException e) {
            return false;
        }

		return true;
	}

	/**
	 * (//host/shareFolder形式をメインに想定)
	 */
	private List<String> getPathList(String... pathArray) {
		Config.registerSmbURLHandler();

		List<String> list = new ArrayList<>();
		list.addAll(List.of(pathArray));

		// ホスト名を除去したパス文字列の取得
		StringBuilder folderPath = new StringBuilder();
		list.stream()
				.map(path -> path.replace("\\", "/"))
				.map(path -> {
					if (path.substring(0, 2).equals("//")) {
						StringBuffer sb = new StringBuffer();
						String[] paths = path.substring(2).split("/");
						for (int i = 0; i < paths.length; i++) {
							if (i == 0) {
								continue;
							}
							String s = paths[i];
							sb.append("/" + s);
						}
						return sb.toString();
					} else if (!path.substring(0, 1).equals("/")) {
						return new StringBuilder().append("/").append(path).toString();
					} else {
						return path;
					}
				})
				.forEach(folderPath::append);

		return Arrays
				.asList(folderPath.toString().split("/")).stream()
				.filter(StringUtils::hasText)
				.collect(Collectors.toList());
	}

おわりに

Path指定が強敵でした。(N敗)

Discussion