apache poi write to temp file instead real file - java

i have a excel file which serve as a template and will be writted and downloaded by several users
I am using apache poi for doing this
The problem is when one user fill the excel (using a gui client) it will be writted directly to the template but as a template it should stay unmodified otherwise next user will have previous user changes ....
The best solution i think is to write to a temp files which will be deleted at user's action exit
Thank you very much
File excelFile = openTemplate();
OPCPackage pkg = OPCPackage.open(excelFile ,PackageAccess.READ_WRITE);
XSSFWorkbook book = new XSSFWorkbook(pkg);
book.setWorkbookType(XSSFWorkbookType.XLSX);
book.setForceFormulaRecalculation(true);
// excel treatments ( sheet , styles etc..)
FileOutputStream out =
new FileOutputStream(TempFile.createTempFile("Export" , ".xlsx"));
book.write(out);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.writeTo(out);
bytes = bos.toByteArray();
out.close();
bos.close();
book.close();
return bytes;

I finally managed to find a solution which resolves my problem
It seems that whenever i call close method on a workbook , it will automatically save the file on hard disk as coded in the library
public void close() throws IOException {
if (this.packageAccess == PackageAccess.READ) {
logger.log(POILogger.WARN,
"The close() method is intended to SAVE a package. This package is open in READ ONLY mode, use the revert() method instead !");
revert();
return;
}
if (this.contentTypeManager == null) {
logger.log(POILogger.WARN,
"Unable to call close() on a package that hasn't been fully opened yet");
return;
}
// Save the content
ReentrantReadWriteLock l = new ReentrantReadWriteLock();
try {
l.writeLock().lock();
if (this.originalPackagePath != null
&& !"".equals(this.originalPackagePath.trim())) {
File targetFile = new File(this.originalPackagePath);
if (!targetFile.exists()
|| !(this.originalPackagePath
.equalsIgnoreCase(targetFile.getAbsolutePath()))) {
// Case of a package created from scratch
save(targetFile);
} else {
closeImpl();
}
} else if (this.output != null) {
save(this.output);
output.close();
}
} finally {
l.writeLock().unlock();
}
// Clear
this.contentTypeManager.clearAll();
}
See http://apache-poi.1045710.n5.nabble.com/Validating-office-files-without-saving-them-td5722653.html and https://bz.apache.org/bugzilla/show_bug.cgi?id=59287
It happens only when a File or OPCPackage is provided to the workbook.
The solution is to work with InputStream : XSSFWorkbook book = new XSSFWorkbook(new FileInputStream(file)); which will not overwrite the template as i wanted

Related

How to get rid of "Save changes?" prompt on xlsx-files created with Apache POI XSSF

After opening and immediately closing an xlsx-file, created with Apache POI XSSF, I get prompted to save unsaved changes. As far as i can tell, this is happening because I am using formulas within the xlsx-file.
According to the javadoc, this should be bypassed by setting XSSFWorkbook.setForceFormulaRecalculation(true)
However, this doesn't solve the problem.
I also tried to manually recalculate the formulas before saving the file without success.
SSCCE:
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class XSSFExample {
public static void main(String[] args) {
// Create workbook and sheet
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet("Sheet 1");
// Create a row and put some cells in it.
Row row = sheet.createRow((short) 0);
row.createCell(0).setCellValue(5.0);
row.createCell(1).setCellValue(5.0);
row.createCell(2).setCellFormula("A1/B1");
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("XSSFExample.xlsx")) {
wb.setForceFormulaRecalculation(false);
System.out.println(wb.getForceFormulaRecalculation()); // prints "false"
XSSFFormulaEvaluator.evaluateAllFormulaCells((XSSFWorkbook) wb); // this doesn't seem to make any difference
wb.write(fileOut);
} catch (IOException ex) {
Logger.getLogger(XSSFExample.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
What can I do to create the file and not get prompted to save it after I opened it the first time?
Update:
As stated here (https://poi.apache.org/spreadsheet/eval.html#recalculation) I also tried another method to manually recalculate with no success. Even re-reading the file after save, recalc and save as a second file doesn't work.
Update 2:
Considering the accepted answer, I was able to solve the problem by adding following lines of code to the above SSCCE:
(Please note that this was just a "quick and dirty" attempt to solve the problem. There are probably a lot of improvements possible).
ZipFile zipFile = new ZipFile("XSSFExample.xlsx");
final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("XSSFExample_NoSave.xlsx"));
for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
ZipEntry entryIn = (ZipEntry) e.nextElement();
if (!entryIn.getName().equalsIgnoreCase("xl/workbook.xml")) {
zos.putNextEntry(entryIn);
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[1024];
int len;
while ((len = (is.read(buf))) > 0) {
zos.write(buf, 0, len);
}
} else {
zos.putNextEntry(new ZipEntry("xl/workbook.xml"));
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[1024];
int len;
while (is.read(buf) > 0) {
String s = new String(buf);
String searchFileVersion = "/relationships\"><workbookPr";
String replaceFileVersion = "/relationships\"><fileVersion appName=\"xl\" lastEdited=\"5\" lowestEdited=\"5\" rupBuild=\"9303\"/><workbookPr";
String searchCalcId = "<calcPr calcId=\"0\"/>";
String replaceCalcId = "<calcPr calcId=\"" + String.valueOf(Integer.MAX_VALUE) + "\"/>";
if (s.contains(searchFileVersion)) {
s = s.replaceAll(searchFileVersion, replaceFileVersion);
}
if (s.contains(searchCalcId)) {
s = s.replaceAll(searchCalcId, replaceCalcId);
}
len = s.trim().length();
buf = s.getBytes();
zos.write(buf, 0, (len < buf.length) ? len : buf.length);
}
}
zos.closeEntry();
}
zos.close();
Even I was facing the same issue but after adding the below line, the issue has been resolved.
wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
PROBLEM
The problem could lie in MS Excel itself (once you are sure that all formulas were calculated and saved in the .xlsx file). According to my testing, Excel will recalculate all formulas during opening if it finds out that the file was last saved by older version of Excel or other application (the point is that the version numbers doesn't match and/or are lower than current version of Excel opening the file) to maintain good compatibility.
SOLUTION
(making Excel think that the .xlsx file was generated by the same Excel version to avoid recalculation)
Excel reads all file versioning info from workbook.xml file located in xl directory inside .xlsx archive (.xlsx is just a zipped archive).
workbook.xml file generated by Apache POI could look like this:
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<workbookPr date1904="false"/>
<bookViews><workbookView activeTab="0"/></bookViews>
<sheets>
<sheet name="new sheet" r:id="rId3" sheetId="1"/>
</sheets>
<calcPr calcId="0"/>
</workbook>
The file generated by Excel 2010 looks like this:
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="9303"/>
<workbookPr defaultThemeVersion="124226"/>
<bookViews><workbookView xWindow="630" yWindow="510" windowWidth="27495" windowHeight="14505"/></bookViews>
<sheets>
<sheet name="new sheet" sheetId="1" r:id="rId1"/>
</sheets>
<calcPr calcId="145621"/>
</workbook>
Notice the <fileVersion> tag completely missing in POI generated file and <calcPr> tag with calcId set to some real value in Excel generated file.
I was able to avoid Excel 2010 automatic formula recalculation (and annoying "Save changes" dialog) by inserting correlated <fileVersion> tag and setting calcId to equal or greater number than the number generated by my current version of Excel to the workbook.xml generated by POI.
More information regarding the workbook.xml format can be found on MSDN Open XML SDK documentation.
I am using Apache POI 5.2.2, open template.xlsx file with just one sheet, clone 1..n new sheets, write cells, delete 1st template sheet, save .xlsx file.
Opening a file in Excel and close gives Save changes? prompt even if did nothing, no #formula cells, no external links or objects in a workbook. I realized if the number of worksheets is different than the original file then prompt is shown.
All sheets had xl/worksheets/sheet1.xml#xr:uid={00000000-0001-0000-0000-000000000000} zero guid.
Text editing sheetX.xml#xr:uid values to {11111111-1111-1111-1111-112233440001}, {11111111-1111-1111-1111-112233440002}, {11111111-1111-1111-1111-112233440003}, .. unique guids fixed a problem.
Using #sobrino's answer this is a modified unzip-zip fix.
public void fixFile(File inputFile, File outputFile) throws IOException {
int count=0;
ZipFile zipFile = new ZipFile(inputFile);
final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputFile));
for (Enumeration<? extends ZipEntry> en = zipFile.entries(); en.hasMoreElements();) {
ZipEntry entryIn = (ZipEntry)en.nextElement();
String name = entryIn.getName();
if(!( name.startsWith("xl/worksheets/") && name.endsWith(".xml")
&& name.indexOf('/', 14)<0 )) {
zos.putNextEntry(entryIn);
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[2*1024];
int len;
while ((len = (is.read(buf))) > 0) {
zos.write(buf, 0, len);
}
} else {
// fix xr:uid="{00000000-0001-0000-0000-000000000000}" zero GUID to avoid "save changes" prompt
// <worksheet ... xr:uid="{11111111-1111-1111-1111-112233440001" ...
count++;
zos.putNextEntry(new ZipEntry(name));
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[2*1024];
int len;
boolean firstRead=true;
while ( (len=is.read(buf)) > 0) {
if(firstRead) {
firstRead=false;
String sData=new String(buf,0,len, "UTF-8");
int delimS=sData.indexOf("xr:uid=\"");
int delimE=sData.indexOf('"', delimS+8);
int delimG=sData.indexOf("-000000000000}", delimS+8);
if(delimG>0 && delimG<=delimE && delimS>0) {
// found zero GUID, replace value
sData=sData.substring(0, delimS+8)
+ String.format("{11111111-1111-1111-1111-11223344%04x}", count)
+ sData.substring(delimE);
zos.write(sData.getBytes("UTF-8"));
} else {
zos.write(buf, 0, len);
}
} else {
zos.write(buf, 0, len);
}
}
}
zos.closeEntry();
}
zos.close();
zipFile.close();
}

Xlsx file corupted when downloading

I created an xlsx file using XSSF from apache-poi and creating a route which returns this file using apache-camel.
The file is created ok, I made all the possible content settings that I found, download is working without any problems on my local machine (windows).
After deploying it to a Unix server (under Tomcat7) and accesing the http path from that server, the file is downloaded but it is corrupted, excel will not open it. I receive the following errors:
"Excel found unreadable content in 'Filename.xlsx'. Do you want to recover the contents of this workbook? If you trust the source of this workbook, click Yes." After I click yes follow up error shows:
"Excel cannot open the file 'Filename.xlsx' because the file format or file extension is not valid. Verify that the file has not been corrupted and that the file extension matches the format of the file."
I am using a processor called from camel to create the Workbook file, and create all the content settings in the processor, this should not be a problem, I logged them outside the processor and all is set. Also everything is working as it should, but only on local PC.
Note: I have the same version of Java/apache-camel/poi/tomcat and so on.. like the server.
Some code snippet:
XSSFWorkbook xlsx = writeToExcel(list);
ByteArrayOutputStream bos = getByteFormat(exchange, xlsx);
exchange.getIn().setHeader("Content-Disposition",
"attachment; filename=ProductExports.xlsx");
exchange.getIn().setHeader(Exchange.CONTENT_TYPE,"application/vnd.openxml");
exchange.getIn().setHeader(Exchange.CONTENT_LENGTH,bos.toByteArray().length);
exchange.getIn().setHeader("Expires","0");
exchange.getIn().setBody(bos.toByteArray());
The getByteFormat() part:
private ByteArrayOutputStream getByteFormat(Exchange exchange,
XSSFWorkbook xlsx) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
xlsx.write(bos);
} finally {
bos.close();
}
return bos;
}
I searched around but didn't find any examples with camel-poi, where people encountered the same problems.
Note: I tried also with HSSF format, and with different CONTENT_TYPE settings, the result is the same.
I guess maybe the problem is caused cause I pass the byte array and somehow Unix systems are interpreting this in a different way, or maybe something with file transfer partitioning. The corrupted file has a bigger dimension then the one which is created normally, probably contains some extra lines, not sure if this matters.
Below the code working fine in Windows and Unix. Hope this will may help you.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
OutputStream outStream = null;
try {
response.setContentType("application/vnd.ms-excel");
response.setHeader
("Content-Disposition", "attachment; filename=Sample.xlsx");
outStream = response.getOutputStream();
Workbook wb = new XSSFWorkbook();
XSSFSheet sheet = wb.createSheet(sheetName) ;
//iterating r number of rows
for (int r=0;r < 5; r++ )
{
XSSFRow row = sheet.createRow(r);
//iterating c number of columns
for (int c=0;c < 5; c++ )
{
XSSFCell cell = row.createCell(c);
cell.setCellValue("Cell "+r+" "+c);
}
}
wb.write(outStream);
outStream.close();
}
catch (Exception e){
throw new ServletException("Exception in Excel Sample Servlet", e);
}
finally{
if (outStream != null)
outStream.close();
}
}

Getting error "Your InputStream was neither an OLE2 stream, nor an OOXML stream" when created file through apache POI

I am trying to check if my excel file already exists. If it doesn't exists, I want to create a new one and if it exists I will delete it and create a new one. I wrote following program but I am getting error at line - workbook= WorkbookFactory.create(instream);
The error is->
java.lang.IllegalArgumentException: Your InputStream was neither an OLE2 stream, nor an OOXML stream
at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:89)
at tryIng.main(tryIng.java:84)
Here is a program ->
try {
String filePath= "C:/Users/pritik/Desktop/t1.xlsx";
File file = new File(filePath);
filePath= file.getAbsolutePath();
xlFile = new File(filePath);
if(xlFile.exists() && !xlFile.isDirectory())
xlFile.delete(); //delete if file already exists.
xlFile.createNewFile();
inStream = new FileInputStream(xlFile);
workbook = WorkbookFactory.create(inStream); // I get error at this line
String sheetName="NewSheet";
Sheet sheet = workbook.createSheet(sheetName);
FileOutputStream fOut = new FileOutputStream(xlFile);
int i,j;
xRows = xTS.length;
xCols = xTS[0].length;
for(i =0;i<xRows;i++)
{
row = sheet.createRow(i);
for(j=0;j<xCols;j++)
{
cell = row.createCell(j);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue(xTS[i][j]);
}
}
workbook.write(fOut);
fOut.flush();
fOut.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Don't create an empty file and try to read it, that won't work. An empty zero byte file is not valid, and can't be loaded Instead, have POI create an new file for you, which you will write later.
Change the code:
if(xlFile.exists() && !xlFile.isDirectory())
xlFile.delete(); //delete if file already exists.
xlFile.createNewFile();
inStream = new FileInputStream(xlFile);
workbook = WorkbookFactory.create(inStream);
To instead be:
if(xlFile.exists() && !xlFile.isDirectory())
xlFile.delete(); //delete if file already exists.
if (xlFile.toString().endsWith(".xls") {
workbook = new HSSFWorkbook();
} else {
workbook = new XSSFWorkbook();
}
Also, if you do want to read an existing file, don't use a stream if you have a file! See this bit of the POI docs for why not.

Java apache POI java.lang.IllegalArgumentException: Position 21504 past the end of the file

I don't know what am I doing wrong. I'm just reading and writing an Excel file, but always get this exception:
java.lang.IllegalArgumentException: Position 21504 past the end of the file
public class example {
public static void main(String[] args) throws Exception {
File destFile = new File("book.xls");
Workbook destBook = WorkbookFactory.create(destFile);
try {
FileOutputStream out = new FileOutputStream(destFile);
destBook.write(out);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
book.xls exists and has the number "1" in each cell from A1 to L50.
You are attempting to write the Workbook back to the same file pathname from which it was read. It appears that WorkbookFactory.create doesn't "release resources" until the Workbook is closed.
Note that in order to properly release resources the Workbook should be closed after use.
When you create the FileOutputStream, you have effectively erased the existing file so that you can write the file data out. However, the Workbook must still rely on the original file being intact. Then, the data to write no longer exists.
You will need to write to a different temporary filename first. Use Apache POI 3.11 or later, so you can call close() on the Workbook, releasing resources.
Close the underlying input resource (File or Stream), from which the Workbook was read. After closing, the Workbook should no longer be used.
This means that the original file must exist until we're done writing it, so the write must be to another (temporary) file.
File srcFile = new File("book.xls");
File destFile = new File("booktemp.xls");
try {
Workbook destBook = WorkbookFactory.create(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
destBook.write(out);
out.close();
destbook.close(); // Available in Apache POI 3.11!
} catch (Exception e) {
e.printStackTrace();
}
Then you can delete the original file and rename the newly created temporary file to the original name.
boolean deleteSuccess = srcFile.delete();
boolean renameSuccess = destFile.renameTo(srcFile);
Pass FileInputStream object in create method instead of File object. It will work.
public static void main(String[] args) throws Exception
FileInputStream destFile = new FileInputStream("book.xls");
Workbook destBook = WorkbookFactory.create(destFile);
try {
FileOutputStream out = new FileOutputStream(destFile);
destBook.write(out);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}

could not delete file neither via java nor manually

I have the following situation, within a servlet a create a file and then have to delete it.
When executing the file, I figured out that the file is still in the server, so I tried to remove it manually, I can't, I get the following message :
this file is opened by another program : javaw.exe
Here is my code :
public class GenerateFile extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("ok");
String fileName = request.getParameter("fileName");
Integer nbrParam = Integer.parseInt(request.getParameter("nbrParam"));
String[] valueParam = new String[nbrParam+1];
for(int i =1;i<=nbrParam;i++)
{ System.out.println(request.getParameter("param"+i));
valueParam[i]=request.getParameter("param"+i);
}
FileInputStream in = new FileInputStream("C:\\Users\\free\\Desktop\\myworkspace\\gestionRH\\WebRoot\\fiches\\"+fileName+".doc");
POIFSFileSystem fs = new POIFSFileSystem(in);
HWPFDocument doc = new HWPFDocument(fs);
Range r = doc.getRange();
for(int i=1;i<=nbrParam;i++)
{ System.out.println("<param"+i+">");
System.out.println(valueParam[i]);
r.replaceText("<param"+i+">", valueParam[i]);
}
File file = new File("C:\\Users\\free\\Desktop\\myworkspace\\gestionRH\\WebRoot\\fiches\\temp");
File temp = File.createTempFile("monfile",".doc",file);
String tempName =temp.getName();
doc.write( new FileOutputStream(temp));
OutputStream out = response.getOutputStream();
response.setContentType("application/rtf");
response.setHeader("Content-Disposition","attachment; filename=Decision");
FileInputStream in1 = new FileInputStream(temp);
byte[] buffer = new byte[4096];
int length;
while ((length = in1.read(buffer)) > 0){
out.write(buffer, 0, length);
}
in1.close();
out.flush();
System.out.println("C:\\Users\\free\\Desktop\\myworkspace\\gestionRH\\WebRoot\\fiches\\temp\\"+tempName);
File f = new File("C:\\Users\\free\\Desktop\\myworkspace\\gestionRH\\WebRoot\\fiches\\temp\\"+tempName);
f.delete();
return null;
}
}
You should close all the file-reading object instances. Besides, if you can delete the file manually, you should close java and then delete it, javaw is the process that launches java outside the console.
The problem is you are creating a new FileOutputStream(tempName) to write on that file, but never closing that outputstream (or another outputstream linked to it).
Do this:
FileOutputStream fos = newFileOutputStream(tempName);
// use it
fos.close(); // CLOSE IT!!
// then you can delete the file
Simplify
Maybe you could do the work another way, without temp files...
by example: doc.write(new FileOutputStream(tempName)) could be replaced by:
doc.write(response.getOutputStream());
This way doc sends its bytes directly to where you need them, not to a temp file eliminating the need for it.
The idea behind input/output streams is composing them. Input/OutputStream are the abstract base classes. And there are a lot of implementations:
based on memory: ByteArrayInput/OutputStream
based on files: FileInputOutputStream
compressing/decompressing to another outputstream: GZipInputOutputStream
and so on
The beauty of it is applying decorator pattern to add functionality. By example:
new GZipOutputStream(new ByteArrayOutputStream());
// creates an outputstreams that compress data received and send it to the other stream
// the BAOS then writes the received bytes to memory
new GZipOutputStream(new FileOutputStream());
// it's the same but sending compressed bytes to a file.
Seems like, you are not closing the file(out), thus it remains with the thread of this action, which is restricting it to get deleted.
Hope it helps.
maybe you should try ProcMon to find out what process exactly holds the file opened
For IO features, I would to suggest to use some kind of jar already provided by community.
For example, common-io.x-x.jar, spring-core.jar
Eg, org.apache.commons.io.FileUtils;
FileUtils.copyDirectory(from, to);
FileUtils.deleteDirectory(childDir);
FileUtils.forceDelete(springConfigDir);
FileUtils.writeByteArrayToFile(file, data);
org.springframework.util.FileSystemUtils;
FileSystemUtils.copyRecursively(from, to);
FileSystemUtils.deleteRecursively(dir);
good luck!
Whenever you open a file handler, you should close it. In a Java application that you want to run for a long period of time, you are strongly recommended to close all unused file handlers soon after you finish working with them.
Examples of common file handlers are FileOutputStream and FileInputstream. Here is a good example of how you open and close the FileOutputStream
FileOutputStream fos = null;
try {
fos = new FileOutputStream(tempName);
// do something
} catch (IOException ex) {
// deal with exceptions
} finally {
// close if fos is not null
if (fos != null) {
fos.close();
}
}
You should never do this:
doc.write( new FileOutputStream(temp));
because you can never close the file handler if it has no refernce to it.

Categories