[bug-65042] support saving package part data in temp files

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1894203 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
PJ Fanning 2021-10-13 18:31:44 +00:00
parent 5b7b8ae1a9
commit defd26493f
8 changed files with 346 additions and 26 deletions

View File

@ -518,7 +518,7 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
* @return output stream for this part * @return output stream for this part
* @see org.apache.poi.openxml4j.opc.internal.MemoryPackagePart * @see org.apache.poi.openxml4j.opc.internal.MemoryPackagePart
*/ */
public OutputStream getOutputStream() { public OutputStream getOutputStream() throws IOException {
OutputStream outStream; OutputStream outStream;
// If this part is a zip package part (read only by design) we convert // If this part is a zip package part (read only by design) we convert
// this part into a MemoryPackagePart instance for write purpose. // this part into a MemoryPackagePart instance for write purpose.
@ -674,9 +674,8 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
* Method that gets the input stream for this part. * Method that gets the input stream for this part.
* *
* @return input stream for this part * @return input stream for this part
* @exception IOException * @throws IOException
* Throws if an IO Exception occur in the implementation * Throws if an IO Exception occur in the implementation method.
* method.
*/ */
protected abstract InputStream getInputStreamImpl() throws IOException; protected abstract InputStream getInputStreamImpl() throws IOException;
@ -684,8 +683,10 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
* Method that gets the output stream for this part. * Method that gets the output stream for this part.
* *
* @return output stream for this part * @return output stream for this part
* @throws IOException
* Throws if an IO Exception occur in the implementation method.
*/ */
protected abstract OutputStream getOutputStreamImpl(); protected abstract OutputStream getOutputStreamImpl() throws IOException;
/** /**
* Save the content of this part and the associated relationships part (if * Save the content of this part and the associated relationships part (if

View File

@ -43,12 +43,7 @@ import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
import org.apache.poi.openxml4j.exceptions.ODFNotOfficeXmlFileException; import org.apache.poi.openxml4j.exceptions.ODFNotOfficeXmlFileException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException; import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException;
import org.apache.poi.openxml4j.opc.internal.ContentTypeManager; import org.apache.poi.openxml4j.opc.internal.*;
import org.apache.poi.openxml4j.opc.internal.FileHelper;
import org.apache.poi.openxml4j.opc.internal.MemoryPackagePart;
import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager;
import org.apache.poi.openxml4j.opc.internal.ZipHelper;
import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller; import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream; import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream;
import org.apache.poi.openxml4j.util.ZipEntrySource; import org.apache.poi.openxml4j.util.ZipEntrySource;
@ -63,6 +58,8 @@ import org.apache.poi.util.TempFile;
public final class ZipPackage extends OPCPackage { public final class ZipPackage extends OPCPackage {
private static final String MIMETYPE = "mimetype"; private static final String MIMETYPE = "mimetype";
private static final String SETTINGS_XML = "settings.xml"; private static final String SETTINGS_XML = "settings.xml";
private static boolean useTempFilePackageParts = false;
private static boolean encryptTempFilePackageParts = false;
private static final Logger LOG = LogManager.getLogger(ZipPackage.class); private static final Logger LOG = LogManager.getLogger(ZipPackage.class);
@ -72,6 +69,34 @@ public final class ZipPackage extends OPCPackage {
*/ */
private final ZipEntrySource zipArchive; private final ZipEntrySource zipArchive;
/**
* @param tempFilePackageParts whether to save package part data in temp files to save memory
*/
public void setUseTempFilePackageParts(boolean tempFilePackageParts) {
useTempFilePackageParts = tempFilePackageParts;
}
/**
* @param encryptTempFiles whether to encrypt temp files
*/
public void setEncryptTempFilePackageParts(boolean encryptTempFiles) {
encryptTempFilePackageParts = encryptTempFiles;
}
/**
* @return whether package part data is stored in temp files to save memory
*/
public boolean useTempFilePackageParts() {
return useTempFilePackageParts;
}
/**
* @return whether package part temp files are encrypted
*/
public boolean encryptTempFilePackageParts() {
return encryptTempFilePackageParts;
}
/** /**
* Constructor. Creates a new, empty ZipPackage. * Constructor. Creates a new, empty ZipPackage.
*/ */
@ -371,8 +396,16 @@ public final class ZipPackage extends OPCPackage {
} }
try { try {
return new MemoryPackagePart(this, partName, contentType, loadRelationships); if (useTempFilePackageParts) {
} catch (InvalidFormatException e) { if (encryptTempFilePackageParts) {
return new EncryptedTempFilePackagePart(this, partName, contentType, loadRelationships);
} else {
return new TempFilePackagePart(this, partName, contentType, loadRelationships);
}
} else {
return new MemoryPackagePart(this, partName, contentType, loadRelationships);
}
} catch (Exception e) {
LOG.atWarn().withThrowable(e).log("Failed to create part {}", partName); LOG.atWarn().withThrowable(e).log("Failed to create part {}", partName);
return null; return null;
} }

View File

@ -86,7 +86,7 @@ public class ZipPackagePart extends PackagePart {
/** /**
* Get the zip entry of this part. * Get the zip entry of this part.
* *
* @return The zip entry in the zip structure coresponding to this part. * @return The zip entry in the zip structure corresponding to this part.
*/ */
public ZipArchiveEntry getZipArchive() { public ZipArchiveEntry getZipArchive() {
return zipEntry; return zipEntry;

View File

@ -0,0 +1,144 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.openxml4j.opc.internal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
import org.apache.poi.poifs.crypt.temp.EncryptedTempData;
import org.apache.poi.util.Beta;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.TempFile;
import java.io.*;
/**
* (Experimental) Encrypted Temp File version of a package part.
*
* @since POI 5.1.0
*/
@Beta
public final class EncryptedTempFilePackagePart extends PackagePart {
private static final Logger LOG = LogManager.getLogger(EncryptedTempFilePackagePart.class);
/**
* Storage for the part data.
*/
private EncryptedTempData tempFile;
/**
* Constructor.
*
* @param pack
* The owner package.
* @param partName
* The part name.
* @param contentType
* The content type.
* @throws InvalidFormatException
* If the specified URI is not OPC compliant.
* @throws IOException
* If temp file cannot be created.
*/
public EncryptedTempFilePackagePart(OPCPackage pack, PackagePartName partName,
String contentType) throws InvalidFormatException, IOException {
this(pack, partName, contentType, false);
}
/**
* Constructor.
*
* @param pack
* The owner package.
* @param partName
* The part name.
* @param contentType
* The content type.
* @param loadRelationships
* Specify if the relationships will be loaded.
* @throws InvalidFormatException
* If the specified URI is not OPC compliant.
* @throws IOException
* If temp file cannot be created.
*/
public EncryptedTempFilePackagePart(OPCPackage pack, PackagePartName partName,
String contentType, boolean loadRelationships)
throws InvalidFormatException, IOException {
super(pack, partName, new ContentType(contentType), loadRelationships);
tempFile = new EncryptedTempData();
}
@Override
protected InputStream getInputStreamImpl() throws IOException {
return tempFile.getInputStream();
}
@Override
protected OutputStream getOutputStreamImpl() throws IOException {
return tempFile.getOutputStream();
}
/**
* @return EncryptedTempData.getSize() always returns -1
*/
@Override
public long getSize() {
return -1;
}
@Override
public void clear() {
try(OutputStream os = getOutputStreamImpl()) {
os.write(new byte[0]);
} catch (IOException e) {
LOG.atWarn().log("Failed to clear data in temp file", e);
}
}
@Override
public boolean save(OutputStream os) throws OpenXML4JException {
return new ZipPartMarshaller().marshall(this, os);
}
@Override
public boolean load(InputStream ios) throws InvalidFormatException {
try (OutputStream os = getOutputStreamImpl()) {
IOUtils.copy(ios, os);
} catch(IOException e) {
throw new InvalidFormatException(e.getMessage(), e);
}
// All done
return true;
}
@Override
public void close() {
tempFile.dispose();
}
@Override
public void flush() {
// Do nothing
}
}

View File

@ -113,17 +113,17 @@ public final class MemoryPackagePart extends PackagePart {
@Override @Override
public boolean load(InputStream ios) throws InvalidFormatException { public boolean load(InputStream ios) throws InvalidFormatException {
try (UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream()) { try (UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream()) {
// Grab the data // Grab the data
IOUtils.copy(ios, baos); IOUtils.copy(ios, baos);
// Save it // Save it
data = baos.toByteArray(); data = baos.toByteArray();
} catch(IOException e) { } catch (IOException e) {
throw new InvalidFormatException(e.getMessage()); throw new InvalidFormatException(e.getMessage());
} }
// All done // All done
return true; return true;
} }
@Override @Override

View File

@ -0,0 +1,142 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.openxml4j.opc.internal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
import org.apache.poi.util.Beta;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.TempFile;
import java.io.*;
/**
* (Experimental) Temp File version of a package part.
*
* @since POI 5.1.0
*/
@Beta
public final class TempFilePackagePart extends PackagePart {
private static final Logger LOG = LogManager.getLogger(TempFilePackagePart.class);
/**
* Storage for the part data.
*/
private File tempFile;
/**
* Constructor.
*
* @param pack
* The owner package.
* @param partName
* The part name.
* @param contentType
* The content type.
* @throws InvalidFormatException
* If the specified URI is not OPC compliant.
* @throws IOException
* If temp file cannot be created.
*/
public TempFilePackagePart(OPCPackage pack, PackagePartName partName,
String contentType) throws InvalidFormatException, IOException {
this(pack, partName, contentType, false);
}
/**
* Constructor.
*
* @param pack
* The owner package.
* @param partName
* The part name.
* @param contentType
* The content type.
* @param loadRelationships
* Specify if the relationships will be loaded.
* @throws InvalidFormatException
* If the specified URI is not OPC compliant.
* @throws IOException
* If temp file cannot be created.
*/
public TempFilePackagePart(OPCPackage pack, PackagePartName partName,
String contentType, boolean loadRelationships)
throws InvalidFormatException, IOException {
super(pack, partName, new ContentType(contentType), loadRelationships);
tempFile = TempFile.createTempFile("poi-package-part", ".tmp");
}
@Override
protected InputStream getInputStreamImpl() throws IOException {
return new FileInputStream(tempFile);
}
@Override
protected OutputStream getOutputStreamImpl() throws IOException {
return new FileOutputStream(tempFile);
}
@Override
public long getSize() {
return tempFile.length();
}
@Override
public void clear() {
try(OutputStream os = getOutputStreamImpl()) {
os.write(new byte[0]);
} catch (IOException e) {
LOG.atWarn().log("Failed to clear data in temp file", e);
}
}
@Override
public boolean save(OutputStream os) throws OpenXML4JException {
return new ZipPartMarshaller().marshall(this, os);
}
@Override
public boolean load(InputStream ios) throws InvalidFormatException {
try (OutputStream os = getOutputStreamImpl()) {
IOUtils.copy(ios, os);
} catch(IOException e) {
throw new InvalidFormatException(e.getMessage(), e);
}
// All done
return true;
}
@Override
public void close() {
if (!tempFile.delete()) {
LOG.atInfo().log("Failed to delete temp file; may already have been closed and deleted");
}
}
@Override
public void flush() {
// Do nothing
}
}

View File

@ -57,7 +57,7 @@ public class XSLFFontData extends POIXMLDocumentPart {
return getPackagePart().getInputStream(); return getPackagePart().getInputStream();
} }
public OutputStream getOutputStream() { public OutputStream getOutputStream() throws IOException {
final PackagePart pp = getPackagePart(); final PackagePart pp = getPackagePart();
pp.clear(); pp.clear();
return pp.getOutputStream(); return pp.getOutputStream();

View File

@ -59,7 +59,7 @@ public final class XSLFObjectData extends POIXMLDocumentPart implements ObjectDa
} }
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() throws IOException {
final PackagePart pp = getPackagePart(); final PackagePart pp = getPackagePart();
pp.clear(); pp.clear();
return pp.getOutputStream(); return pp.getOutputStream();