XmlValueDisconnectedException。
POIでOOXMLを使う際、避けては通れない関門のようです。
僕の環境でも、いつの間にか発生するようになってしまいました。
単にCell#getCellStyle()を呼んでいるだけなのに発生する始末。
というわけで、半日かけて原因調査です。
原因
僕のケースでは、Workbook#write(OutputStream)を呼び出していたことが原因でした。
何故そのような仕様なのかわかりませんが、Workbook#write(OutputStream)を一度でも呼び出すと、そのワークブックは内部的に「使用済み」的な状態になる(クローズ済みのストリームのようなもの)ようです。
従ってそれ以降の処理でXmlValueDisconnectedExceptionが発生するようになってしまいます。
以下の様な使い方をしていました。
class FooReportingLogic { public void doSomething(final InputStream data) throws Exception { final Workbook original = new XSSFWorkbook(data); final Workbook cloned = WorkbookUtil.cloneBlankWorkbook(original); for (final Sheet sheet : sheetsOf(original)) { for (final Row row : sheet) { for (final Cell cell : row) { // XmlValueDisconnectedExceptionが発生!! final CellStyle orginalStyle = cell.getCellStyle(); // ...(略) } } } // ...(略) } /** シートのリストを取得する */ private static List<Sheet> sheets(Workbook book) { // ...(略) } } /** このクラスは何らかの事情で変更不可とする */ final class WorkbookUtil { /** * ワークブックを複製し、シートを全て削除してまっさらにする。 */ public static Workbook cloneBlankWorkbook(final Workbook book) throws Exception { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); // ここで元のワークブックのwriteを呼び出してしまっているため、 // 元のワークブックは「使用済み」になってしまう book.write(baos); final Workbook cloned = new XSSFWorkbook( new ByteArrayInputStream(baos.toByteArray())); for (int i = cloned.getNumberOfSheets(); 0 < i; i--) { cloned.removeSheetAt(i - 1); } return cloned; } }
対応策
要するにWorkbook#writeを呼んでしまったワークブックを再利用しなければ良いだけということで、以下のようにしました。
class FooReportingLogic { public void doSomething(final InputStream data) throws Exception { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtils.copy(data, baos); final Workbook original = new XSSFWorkbook(new ByteArrayInputStream(baos.toByteArray())); // 複製元としてoriginalを使わないようにする final Workbook cloned = WorkbookUtil.cloneBlankWorkbook( new XSSFWorkbook(new ByteArrayInputStream(baos.toByteArray()))); // ...(略) } // ...(略) }