Javaドライバ2

概要

Java用ドライバは、ストリーム(java.io.InputStream/java.io.OutputStream)からストリームへの変換ができることが特徴です。 ユーティリティークラスを利用して、ファイルからストリーム、ストリームからファイル、ファイルからファイルなど、 あらゆる入出力に対応できます。

また、サーブレット/JSPの出力をキャプチャして変換するためのクラスが用意されています。 PDFのもととなるテンプレートをJSP, JSF, Velocity, Tapestryなど、 ウェブ開発で広く使われているJavaベースのテンプレート言語によりデザインできます。

ドライバの準備

Java用ドライバはGitHubの https://github.com/zamasoftnet/cti.java で公開されています。JitPackを利用してMavenまたはGradleで取得することができます。 最新バージョンはリリースページで確認してください。

Mavenの場合は pom.xml に以下を追加してください。


  
    jitpack.io
    https://jitpack.io
  


  
    com.github.zamasoftnet
    cti.java
    vX.X.X
  
]]>

Gradleの場合は build.gradle に以下を追加してください。

repositories {
    maven { url 'https://jitpack.io' }
}
dependencies {
    implementation 'com.github.zamasoftnet:cti.java:vX.X.X'
}

Mavenなどのビルドツールを使わない場合は、GitHubのリリースページからアーカイブをダウンロードすることもできます。

https://github.com/zamasoftnet/cti.java/releases

アーカイブには以下が含まれています。

cti-driver-x.x.x.jar
ドライバ本体(CTIP、REST、CLIが利用可能)
cti-driver-min-x.x.x.jar
最小構成のドライバ(REST、CLIは利用不可)
apidoc
APIドキュメント(Javadoc)
lib
サンプルのコンパイルに必要なライブラリ
examples
サンプルプログラム

ドライバの窓口となるクラスはjp.cssj.cti2.CTIDriverManagerです。 例えばlocalhostの8099番ポートで起動しているcopperdに、ユーザーID"user"、パスワード"kappa"で接続するには、以下のようにします。

//ドライバクラスのインポート
import jp.cssj.cti2.CTIDriverManager;
import jp.cssj.cti2.CTISession;
...

CTISession session = CTIDriverManager.getSession("ctip://127.0.0.1:8099/", "user",
		"kappa");

//各種操作
...

Java用ドライバはHTTP/RESTによる接続にも対応しています

タイムアウトの設定

Java版ドライバ2.1.4以降から、一定時間通信がない状態で自動的に通信を切断するタイムアウトに対応しています。

以下のようにURLパラメータで、timeoutをミリ秒単位で指定できます。

ctip://127.0.0.1:8099/?timeout=10000

ctips, http等他のプロトコルではタイムアウトは無効です。

APIの概要

ここではAPIによるアクセスの概要で説明した各手順に対応する関数を列挙します。 各関数の詳細は オンラインのAPIドキュメント(Javadoc)を参照してください。

サーバーへの接続・認証

サーバー情報の取得

メッセージハンドラ・プログレスリスナの設定

出力先の設定

プロパティの設定

ソースリゾルバの設定

リソースの送信

本体の送信・変換

複数の結果の結合

処理の中断・リセット・通信の終了

サンプル

次の例は、ストリームに送ったHTMLをPDFに変換してファイルとして保存します。

");
					out.println("");
					out.println("");
					out.println("");
					out.println("サンプル");
					out.println("");
					out.println("");
					out.println("

Hello World!

"); out.println(""); out.println(""); } finally { out.close(); } } } finally { // セッションを閉じる(忘れやすいので注意!) session.close(); } } }]]>

次の例は、サーバー側からネットワーク上のウェブページアクセスしてPDFに変換します。

他のサンプルはドライバのexamples/srcに収められています。

プログラミングのポイント

CTISessionHelperの利用

結果の出力先、リソースの送信、ファイルの変換等のよく使われる操作が、 jp.cssj.cti2.helpers.CTISessionHelper のstaticメソッドにまとめられています。

例えば、事前に関連するCSSを送信してHTMLファイルを変換する処理は、次のように簡単に書けます。

...
CTIDriverManager.sendResourceFile(session, new File("test.css"), "text/css", "UTF-8");
CTIDriverManager.transcodeFile(session, new File("test.html"), "text/html", "UTF-8");
...

繰り返し処理

出力先を変え、transcodeメソッドを繰り返し呼び出すことで、 同じセッションで何度もドキュメントを変換することができます。 送信済みのリソース、設定済みのプロパティは同じセッションで維持されます。 同じセッションのまま初期状態に戻すには reset を呼び出してください。

出力先(Results)の設定

出力先が単一のファイルやストリームの場合は、CTISessionHelperの setResultFile か、 setResultStream を使ってください。 これらのメソッドは内部的にjp.cssj.cti2.results.SingleResultクラスを使用しています。

jp.cssj.cti2.results.Resultsインターフェースは、 複数の出力結果を受け取るためのインターフェースです。 CTISessionのsetResultsメソッドに渡します。

複数の結果をファイルとして出力する場合は、 jp.cssj.cti2.results.DirectoryResults を使用してください。 このクラスは、指定したディレクトリに、1から開始する連番の前後に指定した文字列をくっつけたファイル名で結果を出力します。 次の例では変換結果の各ページを、resultsディレクトリ内に"image[通し番号].jpeg"という名前で別々のJPEG画像として出力します。

...
session.property("output.type", "image/jpeg");
session.setResults(new DirectoryResults(new File("results"), "image", ".jpeg"));
...

さらに複雑な処理が必要な場合は、Resultsインターフェースを実装するクラスを用意する必要があります。 Resultsインターフェースは jp.cssj.rsr.RandomBuilder に依存しますが、RandomBuilderにはファイルとストリームにデータを出力する実装 (jp.cssj.rsr.impl.FileRandomBuilder, jp.cssj.rsr.impl.StreamRandomBuilder) が用意されています。

サーバーから要求されたリソースの送信(SourceResolver)

CTISessionのsetSourceResolverで、 ソースリゾルバ(jp.cssj.resolver.SourceResolver) を設定すると、サーバーから要求されたリソースを都度送信できるようになります。

jp.cssj.resolver.composite.CompositeSourceResolverのstaticメソッド、 createGenericCompositeSourceResolver を呼び出すと、file:, http:, data:で始まるURIによのリソースを取得できるSourceResolverの実装が返されます。

CompositeSourceResolverをそのまま使用すると、クライアントのファイルシステム上のファイルをドキュメントから参照可能になってしまうため、注意してください。 次の例のように jp.cssj.resolver.restricted.RestrictedSourceResolver を使用すると、アクセス制限をかけることができます。

...
RestrictedSourceResolver resolver = new RestrictedSourceResolver(
		CompositeSourceResolver.createGenericCompositeSourceResolver());
resolver.include("file:/home/miyabe/data/**");
session.setSourceResolver(resolver);
...

MetaSource

CTISessionの resource, transcode メソッド等では、データの仮想URI、MIME型、キャラクタ・エンコーディング、予測されるデータサイズを MetaSource インターフェースにより渡します。

通常は用意されている jp.cssj.resolver.helpers.MetaSourceImpl という実装を利用してください。

複数の結果の結合

複数の結果を結合したものを得るためには、 setContinuous(true) を呼び出した後、transcodeを複数回呼び出し、最後に join を呼び出してください。

...
session.setContinuous(true);
CTIDriverManager.sendResourceFile(session, new File("test.css"), "text/css", "UTF-8");
CTIDriverManager.transcodeFile(session, new File("test.html"), "text/html", "UTF-8");
session.transcode(URI.create("http://print.cssj.jp/"));
session.join();
...

abortによる中断

CTISessionのabort メソッドは文書の変換処理を中断しますが、transcodeメソッドは処理の間ブロックするため、別スレッドからabortを呼び出す必要があります。 abortは引数によって、強制的に中断するモードと、きりのよいところまで処理して、一応利用可能な結果を出力するモードの2つがあります。 後者のモードは、例えば大きなPDFファイルを出力中に処理を中断して、途中までの出力結果を見たい場合に有用です。 ただし、1ページ目を出力される前に中断してしまった場合など、読み込み可能なデータが出力できないことも起こり得ます。

サーブレット/JSPでの利用

以下はjavax.sevletを使用する場合の例です。 jakarta.servletを使用する場合は、jp.cssj.cti2.helpers.jakartaパッケージ内のクラスを使ってください。

サーブレットで、クライアントに対してドキュメントの変換結果を送る場合は jp.cssj.cti2.helpers.ServletHelper クラスの setServletResponse メソッドを使ってください。 このメソッドは内部で jp.cssj.cti2.helpers.ServletResponseResults クラスを使用しており、出力結果に合わせてContent-Type, Content-Lengthヘッダを適切に設定します。

サーブレットやJSPが出力するデータをキャプチャしてCTISessionに渡す場合は、 jp.cssj.cti2.helpers.CTIHttpServletResponseWrapper クラスを使用してください。 このクラスは、キャプチャしたデータをリソースか、メインドキュメントとしてCTISessionに渡します。 ServletResponseをCTIHttpServletResponseWrapperによりラップして、 RequestDispatcherにより、他のサーブレット/JSPに転送すると、 転送先のサーブレット/JSPによる出力をキャプチャします。 あるいは、フィルタを使う方法があります。

次に紹介するサンプルプログラムは、ドライバのexamples/webappディレクトリにあります。 このサンプルは、source.jspの出力を2通りの方法で変換します。 1つめは、/pdf/ で始まるパスへのアクセスを、サーブレットで転送する方法です。 /pdf/source.jspに対するアクセスを、RequestDispatcherにより/source.jspに転送し、転送先の出力を変換します。 2つめは、/source.jspに対するアクセスを文字通りフィルタリングして変換する方法です。

それぞれjp.cssj.cti2.examples.SampleHttpServletという名前のサーブレットと、 jp.cssj.cti2.examples.SampleFilterという名前のフィルタを使う場合のweb.xmlの記述は次のとおりです。


 
  
    sample-filter
    jp.cssj.cti2.examples.SampleFilter
  
  
  
    sample-filter
    /source.jsp
  
         
  
    sample-servlet
    jp.cssj.cti2.examples.SampleHttpServlet
  

  
    sample-servlet
    /pdf/*
  
  
]]>

次が、サーブレットの実装です。 HttpServletRequestのgetPathInfoにより、ユーザーがアクセスしたアドレスの /pdf の後に続くパスを取得し、 レスポンスをCTIHttpServletResponseWrapperでラップして、そのパスに転送します。

例えば、ユーザーが http://localhost:8180/webapp/pdf/source.jsp にアクセスすると、 source.jsp の出力をPDF変換したものが返されます。

package jp.cssj.cti2.examples;

import java.io.IOException;
import java.net.URI;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jp.cssj.cti2.CTIDriverManager;
import jp.cssj.cti2.CTISession;
import jp.cssj.cti2.helpers.CTIHttpServletResponseWrapper;

public class SampleHttpServlet extends HttpServlet {
	private static final long serialVersionUID = 0L;

	/** 接続先。 */
	private static final URI SERVER_URI = URI.create("ctip://127.0.0.1:8099/");

	/** ユーザー。 */
	private static final String USER = "user";

	/** パスワード。 */
	private static final String PASSWORD = "kappa";

	protected void doGet(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
			// 出力先をレスポンスに設定
			ServletHelper.setServletResponse(session, res);

			// PATH_INFOのアドレスに転送
			String path = ((HttpServletRequest) req).getPathInfo();

			// 転送先のサーブレットが出力したコンテンツを変換
			CTIHttpServletResponseWrapper ctiRes = new CTIHttpServletResponseWrapper(
					res, session, URI.create(path));
			try {
				req.getRequestDispatcher(path).forward(req, ctiRes);
			} finally {
				ctiRes.close();
			}
	}
}

次は、フィルタの実装です。 単にレスポンスをCTIHttpServletResponseWrapperでラップして処理を次に渡すだけです。

例えば、ユーザーが http://localhost:8180/webapp/source.jsp にアクセスすると、 source.jsp の出力をPDF変換したものが返されます。

package jp.cssj.cti2.examples;

import java.io.IOException;
import java.net.URI;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jp.cssj.cti2.CTIDriverManager;
import jp.cssj.cti2.CTISession;
import jp.cssj.cti2.helpers.CTIHttpServletResponseWrapper;

public class SampleFilter implements Filter {
	/** 接続先。 */
	private static final URI SERVER_URI = URI.create("ctip://127.0.0.1:8099/");

	/** ユーザー。 */
	private static final String USER = "user";

	/** パスワード。 */
	private static final String PASSWORD = "kappa";

	private FilterConfig config;

	public void init(FilterConfig config) throws ServletException {
		this.config = config;
	}

	public void doFilter(ServletRequest _req, ServletResponse _res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) _req;
		HttpServletResponse res = (HttpServletResponse) _res;
		CTISession session = CTIDriverManager.getSession(SERVER_URI, USER,
				PASSWORD);
		try {
			// 出力先をレスポンスに設定
			ServletHelper.setServletResponse(session, res);

			// 基底URLとしてコンテキスト以降のパスを使う
			URI uri = URI.create(req.getRequestURI().substring(
					req.getContextPath().length()));
			
			// サーブレットが出力したコンテンツを変換
			CTIHttpServletResponseWrapper ctiRes = new CTIHttpServletResponseWrapper(
					(HttpServletResponse) res, session, uri);
			try {
				chain.doFilter(req, ctiRes);
			} finally {
				ctiRes.close();
			}
		} finally {
			session.close();
		}
	}

	public void destroy() {
		// ignore
	}
}

前記の例では、source.jspと一緒に置かれたCSSや画像が読み込まれません。 これらを読み込むようにするには、次のようなSourceResolverを用意します。

class ServletContextResolver implements SourceResolver {
	protected final ServletContext context;

	public ServletContextResolver(ServletContext context) {
		this.context = context;
	}

	public Source resolve(URI uri) throws IOException {
		// コンテキストに置かれたファイルを取得する
		URL url = this.context.getResource(uri.toString());
		if (url == null) {
			throw new FileNotFoundException(uri.toString());
		}
		try {
			return new URLSource(url);
		} catch (URISyntaxException e) {
			IOException ioe = new IOException();
			ioe.initCause(e);
			throw ioe;
		}
	}

	public void release(Source source) {
		((URLSource) source).close();
	}
}

次のようにサーブレット内でこのSourceResolverを設定すると、 source.jspからの相対パスでCSSや画像にアクセスできるようになります。

	session.setSourceResolver(new ServletContextResolver(this
			.getServletContext()));

ただし、この方法では動的に生成したCSSや画像にはアクセスできませんので、ご注意ください。 全てを動的に変換する場合は、Session.transcodeメソッドを呼び出して、 ローカルホストのサーブレットコンテナにCopper PDFからアクセスするのがよいでしょう。

ソースコード

ドライバのソースコードはGitHubで公開しています。 https://github.com/zamasoftnet/cti.java

Copper PDFのライブラリに直接アクセスする

JavaではCopper PDFサーバーを起動せずに、直接Copper PDFのライブラリを使用することができます。 このためには、以下のようにドライバの接続先として特別なURIである"copper:direct:"を設定します。

import java.io.File;
import java.net.URI;

import jp.cssj.cti2.CTIDriverManager;
import jp.cssj.cti2.CTISession;
import jp.cssj.cti2.helpers.CTISessionHelper;
import jp.cssj.resolver.composite.CompositeSourceResolver;

public class DirectJava {
	public static void main(final String[] args) throws Exception {
		try (CTISession session = CTIDriverManager.getSession(URI
				.create("copper:direct:"))) {
			CTISessionHelper.setResultFile(session, new File("test.pdf"));
			CompositeSourceResolver resolver = CompositeSourceResolver
					.createGenericCompositeSourceResolver();
			session.setSourceResolver(resolver);
			session.transcode(URI
					.create("https://copper-pdf.com/"));
		};
	}
}

こうして得られたCTISessionの使用方法は通常通りドライバを使う場合と変わりありません。 また、プログラムのコンパイルも通常通りドライバのjarをクラスパスに加えることで可能です。

しかし、当然ながらプログラムの実行時にはCopper PDFのlibディレクトリ内にあるjarをクラスパスに加える必要があります。 また、Javaのシステムプロパティjp.cssj.copper.config, jp.cssj.driver.default にそれぞれ設定ディレクトリとdefault.propertiesファイルへのパスを設定する必要があります。

例えば、Windowsのバッチファイルにより起動する場合は次のようにします。 この例では"C:\CopperPDF"に配置したCopper PDFを使用し、カレントディレクトリに置かれたDirectJava.classを実行します。

set COPPER_DIR="C:\CopperPDF"
set CONFIG_DIR="%COPPER_DIR%\conf"
set DEFAULT_FILE="%COPPER_DIR%\conf\profiles/default.properties"
set LIB_DIR="%COPPER_DIR%\lib"

java -cp .;%LIB_DIR%\* -Djp.cssj.copper.config=%CONFIG_DIR% -Djp.cssj.driver.default=%DEFAULT_FILE% DirectJava

Linux等でシェルスクリプトにより起動する場合は次のようにします。 この例では、.rpmや.debでインストールしたCopper PDFを使用し、カレントディレクトリに置かれたDirectJava.classを実行します。

#!/bin/sh
CONFIG_DIR="/etc/copper-pdf"
DEFAULT_FILE="/etc/copper-pdf/profiles/default.properties"
LIB_DIR="/usr/share/copper-pdf/lib"

java -cp .:$LIB_DIR/* -Djp.cssj.copper.config=$CONFIG_DIR -Djp.cssj.driver.default=$DEFAULT_FILE DirectJava

JRubyを使う場合

Java VMを利用したRuby実行環境であるJRubyでは、RubyからJava用のドライバを利用することができます。 JRubyは普通のRuby(CRuby)と同じくらいか、時にはそれ以上の性能を発揮します。 また、Javaと併用する手軽なスクリプト言語としても優秀です。ぜひ、JRubyの使用を検討してください。 どうしてもCRubyを使う必要がある場合は、HTTP/RESTインターフェースを利用してください。

JRubyを使う場合、まずJava用ドライバをインストールしてください。 次にcti-driver-2.x.x.jarを適当な場所(/usr/lib/jruby/libなど)に配置してください。

以下の例では、Copper PDFに接続し、Rubyで出力したHTMLをPDFに変換してファイルに保存します。



JRubyからCopper PDFを使う。


DATA
	ensure
		# クローズを忘れないこと!
		out.close;
	end;
	
ensure
	# セッションの終了
	session.close
end
]]>

JRubyではJavaドライバのAPIをそのまま利用することができます。 詳細はJavaドライバのAPIのドキュメントを参照してください。

Jythonを使う場合

Java VMを利用したPython実行環境であるJythonでは、PythonからJava用のドライバを利用することができます。 Jythonは普通のPython(CPython)と同じくらいか、時にはそれ以上の性能を発揮します。 どうしてもCPythonを使う必要がある場合は、HTTP/RESTインターフェースを利用してください。

Jythonを使う場合、まずJava用ドライバをダウンロードしてください。 次にcti-driver-2.x.x.jarを適当な場所(/usr/share/java/など)に配置してください。

以下の例では、Copper PDFに接続し、Pythonで出力したHTMLをPDFに変換してファイルに保存します。



JythonからCopper PDFを使う。


		""")
	finally:
		# クローズを忘れないこと!
		out.close()
finally:
	# セッションの終了
	session.close()
]]>

JythonではJavaドライバのAPIをそのまま利用することができます。 詳細はJavaドライバのAPIのドキュメントを参照してください。