Javaからプリンターへ印刷する

とあるイベントでWebフォームで入力したテキストをプリンターで印刷するプログラムを書いた。その時の備忘録。

フォーム

フォームはGoogle Web Toolkitでサクッと作った。以下はクライアント側のソースコードの一部。テキストボックス二つと送信ボタンというシンプルな物。

public class JagPrint implements EntryPoint, ClickHandler {
	private static final int MAX_PREVIEW = 100;
	final TextBox textBoxTeamName = new TextBox();
	final TextArea textAreaSourceCode = new TextArea();
	final Button buttonSend = new Button("send");

	public void onModuleLoad() {
		final Grid grid = new Grid(2, 2);
		grid.setWidget(0, 0, new Label("team name"));
		grid.setWidget(1, 0, new Label("source code"));
		grid.setWidget(0, 1, textBoxTeamName);
		grid.setWidget(1, 1, textAreaSourceCode);
		textBoxTeamName.setWidth("640px");
		textAreaSourceCode.setSize("640px", "480px");
		RootPanel.get().add(grid);
		RootPanel.get().add(buttonSend);
		buttonSend.addClickHandler(this);
	}

	@Override
	public void onClick(ClickEvent event) {
		if (event.getSource() == buttonSend) {
			final String teamName = textBoxTeamName.getText();
			final String sourceCode = textAreaSourceCode.getText();

			final StringBuilder sb = new StringBuilder();
			sb.append("team name: ").append(teamName).append('\n');
			sb.append("source code:\n");
			sb.append(sourceCode.length() > MAX_PREVIEW ? sourceCode.substring(0, MAX_PREVIEW) + "..." : sourceCode);
			if (!Window.confirm(sb.toString())) {
				return;
			}

			Rpc.Util.get().send(teamName, sourceCode, callbackSend);
			setEnabled(false);
		}
	}

	private final AsyncCallback<Void> callbackSend = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
			Window.alert("done.");
			setEnabled(true);
		}

		@Override
		public void onFailure(Throwable caught) {
			Window.alert(caught.getLocalizedMessage());
		}
	};

	public void setEnabled(boolean b) {
		final FocusWidget[] widgets = { textBoxTeamName, textAreaSourceCode, buttonSend, };
		for (FocusWidget widget : widgets) {
			widget.setEnabled(b);
		}
	}
}

RPC

続いてRPC部分。こちらも定形通り。

@RemoteServiceRelativePath("rpc")
public interface Rpc extends RemoteService {
	public static class Util {
		public static RpcAsync get() {
			return GWT.create(Rpc.class);
		}
	}

	public void send(String teamName, String sourceCode) throws Exception;
}
public interface RpcAsync {
	void send(String teamName, String sourceCode, AsyncCallback<Void> callback);
}

Servlet

最後にServlet部分。swingのJEditorPaneには簡易印刷機能が付いているそうで、これを利用している。以下の例では印刷設定ダイアログを表示しないように設定して、デフォルトプリンタに出力している。Servletの中でswingを使うというのも妙な話だと思う。

@SuppressWarnings("serial")
public class RpcImpl extends RemoteServiceServlet implements Rpc {
	@Override
	public void send(String teamName, String sourceCode) throws Exception {
		sourceCode = sourceCode.replaceAll("\t", "    ");

		final StringBuilder sb = new StringBuilder();
		sb.append("team name: ").append(teamName).append('\n');
		sb.append("source code:\n");
		sb.append(sourceCode);

		final JEditorPane pane = new JEditorPane();
		pane.setText(sb.toString());
		pane.print(null, null, false, null, null, true);
	}
}

いざDeploy

上記のコードをGoogle Web ToolkitでコンパイルしてWindows上でサービスから起動したTomcatで動かそうとすると、以下のエラーメッセージがログに出力され正しく印刷されない。これはTomcatがローカルシステムアカウントで動いており、プリンタへのアクセス権限がないからだそうだ。(詳しい話は分からない。)

com.google.gwt.user.client.rpc.SerializationException: Type 'java.awt.print.PrinterException' was not included in the set of types which can be serialized by this SerializationPolicy or its Class object could not be loaded. For security purposes, this type will not be serialized.: instance = java.awt.print.PrinterException: No print service found.
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:614)
	at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:126)
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:152)
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:534)
	at com.google.gwt.user.server.rpc.RPC.encodeResponse(RPC.java:616)
	at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:390)
	at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:588)
	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:208)
	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:248)
	at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:306)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:541)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:383)
	at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:284)
	at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:322)
	at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:1684)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

これを解決するには、「コントロールパネル」->「管理ツール」->「サービス」->「Apache Tomcat 7」をダブルクリックして、ログオンダブから現在ログオン中のアカウント情報を入力すれば良い。これでTomcatサービスがプリンタにアクセスすることが出来るようになる。ただし、権限を変更するのでセキュリティ上好ましくはないように思う。