Java用ドライバは、ストリーム(java.io.InputStream/java.io.OutputStream)からストリームへの変換ができることが特徴です。 ユーティリティークラスを利用して、ファイルからストリーム、ストリームからファイル、ファイルからファイルなど、 あらゆる入出力に対応できます。
また、サーブレット/JSPの出力をキャプチャして変換するためのクラスが用意されています。 PDFのもととなるテンプレートをJSP, JSF, Velocity, Tapestryなど、 ウェブ開発で広く使われているJavaベースのテンプレート言語によりデザインできます。
Java用ドライバはCopper PDF本体とは別に配布されています。 http://sourceforge.jp/projects/copper/releases/?package_id=8742 からcti-java 2.x.xをダウンロードしてください。 アーカイブを展開した後にできるlibディレクトリ内のcti-driver-2.x.x.jarがドライバのライブラリです。 このファイルをクラスパスに追加(あるいはアプリケーションのライブラリディレクトにコピー)してください。
ドライバの窓口となるクラスは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による接続にも対応しています。
ここではAPIによるアクセスの概要で説明した各手順に対応する関数を列挙します。 各関数の詳細はドライバのapidocディレクトリ内のJavadocか、 オンラインのAPIドキュメントを参照してください。
次の例は、ストリームに送ったHTMLをPDFに変換してファイルとして保存します。
package jp.cssj.cti2.examples;
import java.io.File;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URI;
import jp.cssj.cti2.CTIDriverManager;
import jp.cssj.cti2.CTISession;
import jp.cssj.cti2.helpers.CTIMessageHelper;
import jp.cssj.cti2.helpers.CTISessionHelper;
import jp.cssj.resolver.helpers.MetaSourceImpl;
public class Example1 {
/** 接続先。 */
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";
public static void main(String[] args) throws Exception {
// 接続する
CTISession session = CTIDriverManager.getSession(SERVER_URI, USER,
PASSWORD);
try {
// test.pdfに結果を出力する
File file = new File("test.pdf");
CTISessionHelper.setResultFile(session, file);
// リソースの送信
{
PrintWriter out = new PrintWriter(new OutputStreamWriter(
session.resource(new MetaSourceImpl(URI.create("style.css"),
"text/html")), "UTF-8"));
try {
// CSSを出力
out.println("p {color: Red;}");
} finally {
out.close();
}
}
// 出力先ストリームを取得
{
PrintWriter out = new PrintWriter(new OutputStreamWriter(
session.transcode(new MetaSourceImpl(URI.create("."),
"text/html")), "UTF-8"));
try {
// 文書を出力
out.println("<html>");
out.println("<head>");
out.println("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
out.println("<link rel='StyleSheet' type='text/css' href='style.css'>");
out.println("<title>サンプル</title>");
out.println("</head>");
out.println("<body>");
out.println("<p>Hello World!</p>");
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
}
} finally {
// セッションを閉じる(忘れやすいので注意!)
session.close();
}
}
}
次の例は、サーバー側からネットワーク上のウェブページアクセスしてPDFに変換します。
package jp.cssj.cti2.examples;
import java.io.File;
import java.net.URI;
import jp.cssj.cti2.CTIDriverManager;
import jp.cssj.cti2.CTISession;
import jp.cssj.cti2.helpers.CTIMessageHelper;
import jp.cssj.cti2.helpers.CTISessionHelper;
public class Example2 {
/** 接続先。 */
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";
public static void main(String[] args) throws Exception {
// 接続する
CTISession session = CTIDriverManager.getSession(SERVER_URI, USER,
PASSWORD);
try {
// test.pdfに結果を出力する
File file = new File("test.pdf");
CTISessionHelper.setResultFile(session, file);
// エラーメッセージを標準出力に表示する
session.setMessageHandler(CTIMessageHelper
.createStreamMessageHandler(System.err));
// ハイパーリンクとブックマークを作成する
session.property("output.pdf.hyperlinks", "true");
session.property("output.pdf.bookmarks", "true");
// http://www.cssj.jp/以下にあるリソースへのアクセスを許可する
session.property("input.include", "http://print.cssj.jp/**");
// ウェブページを変換
session.transcode(URI.create("http://print.cssj.jp/"));
} finally {
// セッションを閉じる(忘れやすいので注意!)
session.close();
}
}
}
他のサンプルはドライバのexamples/srcに収められています。
結果の出力先、リソースの送信、ファイルの変換等のよく使われる操作が、 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 を呼び出してください。
出力先が単一のファイルやストリームの場合は、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) が用意されています。
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);
...
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();
...
CTISessionのabort メソッドは文書の変換処理を中断しますが、transcodeメソッドは処理の間ブロックするため、別スレッドからabortを呼び出す必要があります。 abortは引数によって、強制的に中断するモードと、きりのよいところまで処理して、一応利用可能な結果を出力するモードの2つがあります。 後者のモードは、例えば大きなPDFファイルを出力中に処理を中断して、途中までの出力結果を見たい場合に有用です。 ただし、1ページ目を出力される前に中断してしまった場合など、読み込み可能なデータが出力できないことも起こり得ます。
サーブレットで、クライアントに対してドキュメントの変換結果を送る場合は 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の記述は次のとおりです。
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<filter>
<filter-name>sample-filter</filter-name>
<filter-class>jp.cssj.cti2.examples.SampleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sample-filter</filter-name>
<url-pattern>/source.jsp</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>sample-servlet</servlet-name>
<servlet-class>jp.cssj.cti2.examples.SampleHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sample-servlet</servlet-name>
<url-pattern>/pdf/*</url-pattern>
</servlet-mapping>
</web-app>
次が、サーブレットの実装です。 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からアクセスするのがよいでしょう。
ドライバのソースコードはSourceForge.JPに公開しています。 ドライバのソースコードが必要な方は、以下のガイドを参考にSVNから取得してください。 http://sourceforge.jp/projects/copper/cvs/
CTI Javaのソースコードのターゲットパスは以下の通りです。 http://svn.sourceforge.jp/svnroot/copper/drivers/java/trunk/
現在のところ純粋なRubyによるドライバは用意されていません。 しかし、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に変換してファイルに保存します。
include Java
require "cti-driver.jar" #jarのパスは環境に合わせてください
include_class Java::jp.cssj.cti2.CTIDriverManager
include_class Java::jp.cssj.cti2.CTISession
include_class Java::jp.cssj.cti2.helpers.CTIMessageHelper
include_class Java::jp.cssj.cti2.helpers.CTISessionHelper
include_class Java::jp.cssj.resolver.helpers.MetaSourceImpl
include_class Java::java.io.File
include_class Java::java.net.URI
include_class Java::java.lang.System
# セッションの開始
session = CTIDriverManager.getSession(URI.create("ctip://localhost:8099/"),
"user", "kappa")
begin
# ファイル出力
CTISessionHelper.setResultFile(session, File.new("test.pdf"))
# エラーメッセージを標準エラー出力に表示する
session.setMessageHandler(CTIMessageHelper.createStreamMessageHandler(System.err))
# サーバーへの出力をJavaのOutputStreamからRubyのioに変換して取得
out = session.transcode(MetaSourceImpl.new(URI.create("."), "text/html", "UTF-8")).to_io;
begin
out.puts <<DATA
<html>
<body>
JRubyからCopper PDFを使う。
</body>
</html>
DATA
ensure
# クローズを忘れないこと!
out.close;
end;
ensure
# セッションの終了
session.close
end
JRubyではJavaドライバのAPIをそのまま利用します。 詳細はJavaドライバのAPIのドキュメントを参照してください。