Add WorkbookFactory.create() with a flag to allow to open files read-only, keep the current way of opening read/write as default to not break existing code.

Also adjust Javadoc somewhat.

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1681823 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Dominik Stadler 2015-05-26 19:30:21 +00:00
parent 9017803980
commit 03805d9e20
2 changed files with 129 additions and 40 deletions

View File

@ -30,6 +30,7 @@ import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.poifs.crypt.Decryptor; import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
@ -41,22 +42,22 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/** /**
* Factory for creating the appropriate kind of Workbook * Factory for creating the appropriate kind of Workbook
* (be it {@link HSSFWorkbook} or {@link XSSFWorkbook}), * (be it {@link HSSFWorkbook} or {@link XSSFWorkbook}),
* by auto-detecting from the supplied input. * by auto-detecting from the supplied input.
*/ */
public class WorkbookFactory { public class WorkbookFactory {
/** /**
* Creates a HSSFWorkbook from the given POIFSFileSystem * Creates a HSSFWorkbook from the given POIFSFileSystem
* <p>Note that in order to properly release resources the * <p>Note that in order to properly release resources the
* Workbook should be closed after use. * Workbook should be closed after use.
*/ */
public static Workbook create(POIFSFileSystem fs) throws IOException { public static Workbook create(POIFSFileSystem fs) throws IOException {
return new HSSFWorkbook(fs); return new HSSFWorkbook(fs);
} }
/** /**
* Creates a HSSFWorkbook from the given NPOIFSFileSystem * Creates a HSSFWorkbook from the given NPOIFSFileSystem
* <p>Note that in order to properly release resources the * <p>Note that in order to properly release resources the
* Workbook should be closed after use. * Workbook should be closed after use.
*/ */
public static Workbook create(NPOIFSFileSystem fs) throws IOException { public static Workbook create(NPOIFSFileSystem fs) throws IOException {
@ -67,19 +68,27 @@ public class WorkbookFactory {
throw new IOException(e); throw new IOException(e);
} }
} }
/** /**
* Creates a Workbook from the given NPOIFSFileSystem, which may * Creates a Workbook from the given NPOIFSFileSystem, which may
* be password protected * be password protected
*
* @param fs The {@link NPOIFSFileSystem} to read the document from
* @param password The password that should be used or null if no password is necessary.
*
* @return The created Workbook
*
* @throws IOException if an error occurs while reading the data
* @throws InvalidFormatException if the contents of the file cannot be parsed into a {@link Workbook}
*/ */
private static Workbook create(NPOIFSFileSystem fs, String password) throws IOException, InvalidFormatException { private static Workbook create(NPOIFSFileSystem fs, String password) throws IOException, InvalidFormatException {
DirectoryNode root = fs.getRoot(); DirectoryNode root = fs.getRoot();
// Encrypted OOXML files go inside OLE2 containers, is this one? // Encrypted OOXML files go inside OLE2 containers, is this one?
if (root.hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) { if (root.hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) {
EncryptionInfo info = new EncryptionInfo(fs); EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info); Decryptor d = Decryptor.getInstance(info);
boolean passwordCorrect = false; boolean passwordCorrect = false;
InputStream stream = null; InputStream stream = null;
try { try {
@ -95,18 +104,18 @@ public class WorkbookFactory {
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new IOException(e); throw new IOException(e);
} }
if (! passwordCorrect) { if (! passwordCorrect) {
if (password != null) if (password != null)
throw new EncryptedDocumentException("Password incorrect"); throw new EncryptedDocumentException("Password incorrect");
else else
throw new EncryptedDocumentException("The supplied spreadsheet is protected, but no password was supplied"); throw new EncryptedDocumentException("The supplied spreadsheet is protected, but no password was supplied");
} }
OPCPackage pkg = OPCPackage.open(stream); OPCPackage pkg = OPCPackage.open(stream);
return create(pkg); return create(pkg);
} }
// If we get here, it isn't an encrypted XLSX file // If we get here, it isn't an encrypted XLSX file
// So, treat it as a regular HSSF XLS one // So, treat it as a regular HSSF XLS one
if (password != null) { if (password != null) {
@ -116,53 +125,78 @@ public class WorkbookFactory {
Biff8EncryptionKey.setCurrentUserPassword(null); Biff8EncryptionKey.setCurrentUserPassword(null);
return wb; return wb;
} }
/** /**
* Creates a XSSFWorkbook from the given OOXML Package * Creates a XSSFWorkbook from the given OOXML Package
* <p>Note that in order to properly release resources the *
* Workbook should be closed after use. * <p>Note that in order to properly release resources the
* Workbook should be closed after use.</p>
*
* @param pkg The {@link OPCPackage} opened for reading data.
*
* @return The created Workbook
*
* @throws IOException if an error occurs while reading the data
*/ */
public static Workbook create(OPCPackage pkg) throws IOException { public static Workbook create(OPCPackage pkg) throws IOException {
return new XSSFWorkbook(pkg); return new XSSFWorkbook(pkg);
} }
/** /**
* Creates the appropriate HSSFWorkbook / XSSFWorkbook from * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
* the given InputStream. * the given InputStream.
*
* <p>Your input stream MUST either support mark/reset, or * <p>Your input stream MUST either support mark/reset, or
* be wrapped as a {@link PushbackInputStream}! Note that * be wrapped as a {@link PushbackInputStream}! Note that
* using an {@link InputStream} has a higher memory footprint * using an {@link InputStream} has a higher memory footprint
* than using a {@link File}.</p> * than using a {@link File}.</p>
* <p>Note that in order to properly release resources the *
* <p>Note that in order to properly release resources the
* Workbook should be closed after use. Note also that loading * Workbook should be closed after use. Note also that loading
* from an InputStream requires more memory than loading * from an InputStream requires more memory than loading
* from a File, so prefer {@link #create(File)} where possible. * from a File, so prefer {@link #create(File)} where possible.
* @throws EncryptedDocumentException If the workbook given is password protected *
* @param inp The {@link InputStream} to read data from.
*
* @return The created Workbook
*
* @throws IOException if an error occurs while reading the data
* @throws InvalidFormatException if the contents of the file cannot be parsed into a {@link Workbook}
* @throws EncryptedDocumentException If the workbook given is password protected
*/ */
public static Workbook create(InputStream inp) throws IOException, InvalidFormatException, EncryptedDocumentException { public static Workbook create(InputStream inp) throws IOException, InvalidFormatException, EncryptedDocumentException {
return create(inp, null); return create(inp, null);
} }
/** /**
* Creates the appropriate HSSFWorkbook / XSSFWorkbook from * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
* the given InputStream, which may be password protected. * the given InputStream, which may be password protected.
* <p>Your input stream MUST either support mark/reset, or * <p>Your input stream MUST either support mark/reset, or
* be wrapped as a {@link PushbackInputStream}! Note that * be wrapped as a {@link PushbackInputStream}! Note that
* using an {@link InputStream} has a higher memory footprint * using an {@link InputStream} has a higher memory footprint
* than using a {@link File}.</p> * than using a {@link File}.</p>
* <p>Note that in order to properly release resources the *
* <p>Note that in order to properly release resources the
* Workbook should be closed after use. Note also that loading * Workbook should be closed after use. Note also that loading
* from an InputStream requires more memory than loading * from an InputStream requires more memory than loading
* from a File, so prefer {@link #create(File)} where possible. * from a File, so prefer {@link #create(File)} where possible.</p>
* @throws EncryptedDocumentException If the wrong password is given for a protected file *
* @throws EmptyFileException If an empty stream is given * @param inp The {@link InputStream} to read data from.
* @param password The password that should be used or null if no password is necessary.
*
* @return The created Workbook
*
* @throws IOException if an error occurs while reading the data
* @throws InvalidFormatException if the contents of the file cannot be parsed into a {@link Workbook}
* @throws EncryptedDocumentException If the wrong password is given for a protected file
* @throws EmptyFileException If an empty stream is given
*/ */
public static Workbook create(InputStream inp, String password) throws IOException, InvalidFormatException, EncryptedDocumentException { public static Workbook create(InputStream inp, String password) throws IOException, InvalidFormatException, EncryptedDocumentException {
// If clearly doesn't do mark/reset, wrap up // If clearly doesn't do mark/reset, wrap up
if (! inp.markSupported()) { if (! inp.markSupported()) {
inp = new PushbackInputStream(inp, 8); inp = new PushbackInputStream(inp, 8);
} }
// Ensure that there is at least some data there // Ensure that there is at least some data there
byte[] header8 = IOUtils.peekFirst8Bytes(inp); byte[] header8 = IOUtils.peekFirst8Bytes(inp);
@ -176,51 +210,90 @@ public class WorkbookFactory {
} }
throw new IllegalArgumentException("Your InputStream was neither an OLE2 stream, nor an OOXML stream"); throw new IllegalArgumentException("Your InputStream was neither an OLE2 stream, nor an OOXML stream");
} }
/** /**
* Creates the appropriate HSSFWorkbook / XSSFWorkbook from * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
* the given File, which must exist and be readable. * the given File, which must exist and be readable.
* <p>Note that in order to properly release resources the * <p>Note that in order to properly release resources the
* Workbook should be closed after use. * Workbook should be closed after use.
* @throws EncryptedDocumentException If the workbook given is password protected *
* @param file The file to read data from.
*
* @return The created Workbook
*
* @throws IOException if an error occurs while reading the data
* @throws InvalidFormatException if the contents of the file cannot be parsed into a {@link Workbook}
* @throws EncryptedDocumentException If the workbook given is password protected
*/ */
public static Workbook create(File file) throws IOException, InvalidFormatException, EncryptedDocumentException { public static Workbook create(File file) throws IOException, InvalidFormatException, EncryptedDocumentException {
return create(file, null); return create(file, null);
} }
/** /**
* Creates the appropriate HSSFWorkbook / XSSFWorkbook from * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
* the given File, which must exist and be readable, and * the given File, which must exist and be readable, and
* may be password protected * may be password protected
* <p>Note that in order to properly release resources the * <p>Note that in order to properly release resources the
* Workbook should be closed after use. * Workbook should be closed after use.
* @throws EncryptedDocumentException If the wrong password is given for a protected file *
* @throws EmptyFileException If an empty stream is given * @param file The file to read data from.
* @param password The password that should be used or null if no password is necessary.
*
* @return The created Workbook
*
* @throws IOException if an error occurs while reading the data
* @throws InvalidFormatException if the contents of the file cannot be parsed into a {@link Workbook}
* @throws EncryptedDocumentException If the wrong password is given for a protected file
* @throws EmptyFileException If an empty stream is given
*/ */
public static Workbook create(File file, String password) throws IOException, InvalidFormatException, EncryptedDocumentException { public static Workbook create(File file, String password) throws IOException, InvalidFormatException, EncryptedDocumentException {
return create(file, password, false);
}
/**
* Creates the appropriate HSSFWorkbook / XSSFWorkbook from
* the given File, which must exist and be readable, and
* may be password protected
* <p>Note that in order to properly release resources the
* Workbook should be closed after use.
*
* @param file The file to read data from.
* @param password The password that should be used or null if no password is necessary.
* @param readOnly If the Workbook should be opened in read-only mode to avoid writing back
* changes when the document is closed.
*
* @return The created Workbook
*
* @throws IOException if an error occurs while reading the data
* @throws InvalidFormatException if the contents of the file cannot be parsed into a {@link Workbook}
* @throws EncryptedDocumentException If the wrong password is given for a protected file
* @throws EmptyFileException If an empty stream is given
*/
public static Workbook create(File file, String password, boolean readOnly) throws IOException, InvalidFormatException, EncryptedDocumentException {
if (! file.exists()) { if (! file.exists()) {
throw new FileNotFoundException(file.toString()); throw new FileNotFoundException(file.toString());
} }
try { try {
NPOIFSFileSystem fs = new NPOIFSFileSystem(file); NPOIFSFileSystem fs = new NPOIFSFileSystem(file, readOnly);
return create(fs, password); return create(fs, password);
} catch(OfficeXmlFileException e) { } catch(OfficeXmlFileException e) {
// opening as .xls failed => try opening as .xlsx // opening as .xls failed => try opening as .xlsx
OPCPackage pkg = OPCPackage.open(file); OPCPackage pkg = OPCPackage.open(file, readOnly ? PackageAccess.READ : PackageAccess.READ_WRITE);
try { try {
return new XSSFWorkbook(pkg); return new XSSFWorkbook(pkg);
} catch (IOException ioe) { } catch (IOException ioe) {
// ensure that file handles are closed (use revert() to not re-write the file) // ensure that file handles are closed (use revert() to not re-write the file)
pkg.revert(); pkg.revert();
//pkg.close(); //pkg.close();
// rethrow exception // rethrow exception
throw ioe; throw ioe;
} catch (IllegalArgumentException ioe) { } catch (IllegalArgumentException ioe) {
// ensure that file handles are closed (use revert() to not re-write the file) // ensure that file handles are closed (use revert() to not re-write the file)
pkg.revert(); pkg.revert();
//pkg.close(); //pkg.close();
// rethrow exception // rethrow exception
throw ioe; throw ioe;
} }

View File

@ -70,6 +70,22 @@ public final class TestWorkbookFactory extends TestCase {
// TODO: this re-writes the sample-file?! wb.close(); // TODO: this re-writes the sample-file?! wb.close();
} }
public void testCreateReadOnly() throws Exception {
Workbook wb;
// POIFS -> hssf
wb = WorkbookFactory.create(HSSFTestDataSamples.getSampleFile(xls), null, true);
assertNotNull(wb);
assertTrue(wb instanceof HSSFWorkbook);
wb.close();
// Package -> xssf
wb = WorkbookFactory.create(HSSFTestDataSamples.getSampleFile(xlsx), null, true);
assertNotNull(wb);
assertTrue(wb instanceof XSSFWorkbook);
wb.close();
}
/** /**
* Creates the appropriate kind of Workbook, but * Creates the appropriate kind of Workbook, but
* checking the mime magic at the start of the * checking the mime magic at the start of the