I am developing a webapp (for mobile phones). There is one xhtml page, where I want to show a picture, which is stored locally on my hard drive (for example: D:\pictures\test.jpg).
Since browsers block images when they are located on a local harddrive, I wrote a method in my javabean, where the picture, stored on the localHD, is copied to the webApp directory, when the user enters the xhtml page. After the user leaves the page, the copied file inside the webapp should be deleted.
So when I'm running my app, copying works perfectly and the pictures are displayed correctly. However, when the files should get deleted, I get this errormessage:
java.nio.file.FileSystemException: D:\WebAppPath\src\main\webapp\resources\pics\test.jpg:
The process cannot be accessed because the file is being used by another process.
Strangely enough, after stopping and restarting the application I can delete the same image if it is still in the webApp directory. (But Only once; after re-copying it, I get the error message again.)
Also if I want to delete the file manually, by using Windows explorer, I get the error message that the file can't be deleted because it is used by Java(TM) Platform SE Binary.
So to delete the file (manually or via the bean) I have to wait for a restart of the application, which of course is not an acceptable solution for the end user.
I'm using JSF2.0 with Primefaces and Primefaces Mobile components. My IDE is Netbeans and I use Spring Webflow framework to navigate and trigger actions/methods between the xhtml pages.
Here's the code for the copying method in my JavaBean:
public void copyFotoToLocalhost() {
if (fotoList.size() > 0) {
for (int i = 0; i < fotoList.size(); i++) {
Foto tempPic = fotoList.get(i);
String tempItemName = tempPic.getItemName();
String originalFile = "D:\\localFilepath\\" + tempItemName;
String tempFileName = "D:\\WebAppPath\\src\\main\\webapp\\resources\\pics\\" + tempItemName;
File existTest = new File(tempFileName);
if (existTest.exists() == false) {
try {
File orFile = new File(originalFile);
File tempFile = new File(tempFileName);
InputStream in = new FileInputStream(orFile);
OutputStream out = new FileOutputStream(tempFile);
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
tempFile.setWritable(true);
System.out.println("File copied.");
} catch (FileNotFoundException ex) {
System.out.println(ex.getMessage() + " in the specified directory.");
System.exit(0);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
}
}
Here's the code for the delete method:
public void deleteFotos() {
if (fotoList.size() > 0) {
for (int i = 0; i < fotoList.size(); i++) {
Foto tempPic = fotoList.get(i);
String tempItemName = tempPic.getItemName();
Path tempLocation = Paths.get("D:\\webAppPath\\src\\main\\webapp\\resources\\pics\\" + tempItemName);
fotoList.remove(i);
i--;
try {
Files.deleteIfExists(tempLocation);
System.out.println("sucessfully deleted" + tempPic.getItemName());
} catch (IOException ex) {
Logger.getLogger(WundDokuBean.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Fail # " + tempPic.getItemName());
}
}
fotoList.clear();
}
Do you have an idea, how to fix this?
I hope you understand my problem, if not please tell me which information you need, I'll try to provide it.
There is one xhtml page, where I want to show a picture, which is stored locally on my hard drive (for example: D:\pictures\test.jpg). Since browsers block images when they are located on a local harddrive (...)
I want to clear out a conceptual misunderstanding first: You seem to expect that it would work fine when the browser wouldn't have blocked it. This is completely untrue. You seem to expect that images are inlined in the HTML output. No, they are downloaded individually and independently from the HTML page. If you had continued to use local disk file system paths, then it would have worked only and only if your webpage visitor has also exactly the same file at exactly the same location at their disk file system. In reality, this is obviously not the case. It would only work if both the webbrowser and webserver runs at physically the same machine.
Coming back to your concrete problem of being unable to delete the file, it's is caused because the servletcontainer usually locks the files in expanded WAR folder. I can't tell the exact reason, but that's not relevant here as this whole approach is wrong anyway. This approach would fail when the deployed WAR file is not expanded on disk file system, but instead in server's memory. Also, hardcoding environment-specific disk file system paths is a bad idea. You'd need to edit, rewrite, recompile, rebuild the whole WAR everytime you change the environment. In other words, your webapp is not portable.
You need to keep the files there where they originally are and make them publicly available by a real URL. This can be achieved in 2 general ways:
Add a virtual host to the server config, pointing to D:\localFilepath\. How to achieve that depends on the server used. You didn't tell anything about the server make/version used, but using Spring suggests that you're not being able to use full Java EE stack and are likely using a barebones JSP/Servlet container such as Tomcat. In that case, it's a matter of adding the following line to its /conf/server.xml:
<Context docBase="D:\localFilepath" path="/fotos" />
This way they are available by http://localhost:8080/fotos/*.
Create a servlet which reads files from D:\localFilepath and writes to HTTP response. With Servlet 3.0 and Java 7 it's really a breeze. Here's a kickoff example (nullchecks/file-exist-checks/doHead()/caching/resuming omitted for brevity):
#WebServlet("/fotos/*")
public class FotosServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletExcpetion, IOException {
File file = new File("D:/localFilepath", request.getPathInfo().substring(1));
response.setHeader("Content-Type", getServletContext().getMimeType(file.getName()));
response.setHeader("Content-Length", String.valueOf(file.length()));
Files.copy(file.toPath(), response.getOutputStream());
}
}
That's basically it. This way they're available on http://localhost:8080/contextname/fotos/*.
Related
I've recently inherited a Java API and am having trouble with file uploads. Unfortunately, Java isn't a language I have much experience in so I'm a bit stumped by this.
The MultiPartFile is being received ok, and I can find the file in the temp directory, but when I try to use File.transferTo() to create the final file I just get the below error;
java.nio.file.NoSuchFileException: C:\Users\myUser\AppData\Local\Temp\undertow3706399294849267898upload -> S:\Dev\PolicyData\Temp.xlsx
As I mentioned the temp undertow file exists, and the directory on the S drive also exist, (but there's no Temp.xlsx as my understanding is this should be created by transferTo()). Any solutions I've found to this problem so far are resolved using absolute file paths.
This is a simplified version of the code but the error remains the same.
SpringBoot framework is "1.5.3.RELEASE", running Java 1.8.0_131
ResponseEntity handleFileUpload(#RequestPart(name = "file") MultipartFile file, #PathVariable Long stageFileTypeId) {
if (!file.isEmpty()) {
try {
String filePath = "S:\\Dev\\PolicyData\\Temp.xlsx";
log.info("Upload Path = {}", filePath);
File dest = new File(filePath);
file.transferTo(dest);
return ResponseUtil.wrapOrNotFound(Optional.ofNullable(filePath));
}
catch (Exception ex) {
log.error("An error has occurred uploading the file", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
else {
log.error("An error has occurred, no file was received");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
If you need any more information please let me know.
Thanks,
Neil
The API for MultipartFile is a bit tricky. The transferTo(File) method javadoc states that (bold are mine):
This may either move the file in the filesystem, copy the file in the
filesystem, or save memory-held contents to the destination file. If
the destination file already exists, it will be deleted first.
If the target file has been moved in the filesystem, this operation
cannot be invoked again afterwards. Therefore, call this method just
once in order to work with any storage mechanism.
It seems that the Undertow implementantion already called it to move the in-memory uploaded file to "C:\Users\myUser\AppData\Loca\Temp\undertow3706399294849267898upload" so another transferTo is failing.
I came across the same problem using javax.servlet.http.Part in a Wildfly containter with Undertow.
If you are using Spring framework >= 5.1, you could try the Multipart.transferTo(Path) method, using dest.toPath()
Or you can copy from the inputStream, with something like this:
try (InputStream is = multipartFile.getInputStream()) {
Files.copy(is, dest.toPath());
}
I have an issue that only occurs when the web application is packed into WAR and installed on a Tomcat server - it does not occur during development.
What I do is:
I retrieve Part from the HttpServletRequest, which is the uploaded file:
Part uploadedFile = null;
for(Part part: parts) {
if(part == null)
continue;
if(uploadFieldName.equalsIgnoreCase(part.getName())) {
uploadedFile = part;
break;
}
}
I then store this Part object for later use (it cannot be used immediately). The code is a bit complicated, but essentially akin to:
// List of uploaded files pending
// List declared as:
// protected List<Part> uploadedFiles;
synchronized(this.uploadedFiles) {
this.uploadedFiles.add(uploadedFile);
}
Later on, I try to fetch the file from another thread.
// Assume that the method is synchronized
Part reqrievedFile = myUploadService.getFileFromArray();
synchronized(retrievedFile) {
doSomething(retrievedFile.getInputStream());
Finally I delete the file 5 minutes after use:
retrievedFile.delete();
When testing in Eclipse, this works well, the file is available and I can process it.
But when this feature was added to the test code and uploaded on test Tomcat server, testers experienced this error:
IOException: C:\apache-tomcat-8.5.34\work\Catalina\localhost\Projectname\upload_9357ad4d_193f_40fc_96d6_b6e4b9e3c82a_00000001.tmp
(The system cannot find the file specified)
I have Spring MVC web app running on Tomcat.
I upload a file and save it in the /tmp folder on the file system.
Then I need to show a link to that file in the view (Thymeleaf), so that the user can download the file by clicking on the link. How to do that?
I've heard about configuring Tomcat to allow a specific context to link to a folder on the FS, but not sure how to do that or if that is the only solution. Please help.
The way I approach this is slightly different. Basically I use two controller actions for handling file uploads, one for uploading, and for downloading (viewing) files.
So upload action would save files to some preconfigured directory on the file system, I assume you already have that part working.
Then declare download action similar to this
#Controller
public class FileController {
#RequestMapping("/get-file/{filename}")
public void getFileAction(#RequestParam filename, HttpServletResponse response) {
// Here check if file with given name exists in preconfigured upload folder
// If it does, write it to response's output stream and set correct response headers
// If it doesn't return 404 status code
}
}
If you want to make impossible to download file just by knowing the name, after uploading file, save some meta info to the database (or any other storage) and assign some hash (random id) to it. Then, in getFileAction, use this hash to look for file, not the original filename.
Finally, I would discourage using /tmp for file uploads. It depends on the system/application used, but generally temp directory are meant, as name suggest, for temporary data. Usually it is guaranteed data in the temp directory will stay for "reasonable time", but applications must take into account that content of temp directory can be deleted anytime.
This is the precisely setup that worked for me (Tomcat 8, SpringMVC, boot):
server.xml:
<Context docBase="C:\tmp\" path="/images" />
In the controller:
public String createNewsSource(#ModelAttribute("newsSource") NewsSource source, BindingResult result, Model model,
#RequestParam("attachment") final MultipartFile attachment) {
new NewsSourceValidator().validate(source, result);
if (result.hasErrors()) {
return "source/addNewSource";
}
if (!attachment.isEmpty()) {
try {
byte[] bytes = attachment.getBytes();
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(new File("/tmp/" + attachment.getOriginalFilename())));
stream.write(bytes);
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
source.setLogo("images/" + attachment.getOriginalFilename());
newsSourceService.createNewsSourceIfNotExist(source);
return "redirect:/sources/list";
}
As you can see I am saving the file to /tmp, but in the DB (source.setLogo()), I am pointing to images as mapped in server.xml
Here's where I found about Tomcat config:
If the images are all located outside the webapp and you want to have
Tomcat's DefaultServlet to handle them, then all you basically need to
do in Tomcat is to add the following Context element to
/conf/server.xml inside tag:
This way they'll be accessible through http://example.com/images/....
SO answer to a similar question
So I pushed my java app to a server, pretty excited about that.
Now I want to test something, how can I save the posted data to my servlet to a file, and the filename should be a unique guid.
I have this so far:
public class TestServlet extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, IOException {
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, IOException {
PrintWriter printWriter = response.getWriter();
printWriter.print("hello, world from testservlet!");
}
}
So assuming the http posted data (say around 50K) will be posted to the field 'payload', how can I grab the posted text, and save it to a file, with the filename being a GUID.
Does java have a construct to clean up an open file, like in c#:
using(var file = new ....)
{
// write to file
}
That closes the connection and cleans up memory etc.
Also, do I need to set special permissions for tomcat to save this file?
I just set things up by default right now (just playing around on a VPS) using ubuntu 11, installed tomcat6.
Thanks.
You can user request to read the "payload", see the API doc for ServletRequest:
request.getParameter("payload");
You can use File to create the file, see AP doc:
File newFile = new File("fileName");
boolean isCreated = newfile.createNewFile();
You can write to the file as follows,
BufferedWriter out = new BufferedWriter(new FileWriter(newFile));
out.write(payLoad);
out.close();
For GUID you see this Create a GUID in Java
And for the clean up, you don't have to worry about it in Java, it's Garbage Collector ( What is the garbage collector in Java? ) does it for you automatically when the reference goes out of scope.
But you should close the resources like out.close to release it back to the system when you are done with it.
Also, do I need to set special permissions for tomcat to save this file?
You do not need to do that because tomcat is just a server, it's more related to the file system (OS). I use Glassfish on Unix and I don't need to do anything like that to create file.
Now I want to test something, how can I save the posted data to my servlet to a file, and the filename should be a unique guid.
Use File#createTempFile() to create a file with an unique ID in the given folder.
File file = File.createTempfile("prefix-", ".ext", "/path/to/files");
// ...
See also:
Saving uploaded file in specific location
Does java have a construct to clean up an open file, like in c#: using?
Only in Java 7 which is already been out for some time.
try (FileWriter writer = new FileWriter(file)) {
writer.write(content);
}
which is equivalent to
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.write(content);
} finally {
if (writer != null) writer.close();
}
See also:
"using" keyword in java
Also, do I need to set special permissions for tomcat to save this file?
The user who has started Tomcat should indeed have the file write permissions on the given directory.
In the future please ask separate questions in separate SO questions.
Java 7 has a new try with resources construct that will take care of closing the file for you. Otherwise... just close the file; no big deal.
As far as "special permissions", as long as the user Tomcat is running under can access the directory in question, there's no issue. I'd recommend against storing it under the webapp directories, though (and if it's deployed as a war you may not be able to anyway). Keep uploaded files in a known, but separate, directory.
Basically my problem is a that I need to copy an uploaded file (which is placed in a temporary folder on my server providers Tomcat ie. not a local Tomcat).
The code I'm using works when I deploy my project on my local machine but stops working when I deploy it live.
I've found out that it has something to with my permissions in java.policy.
What I need to find out is how do I get access to the folder in which Tomcat stores the temporary file using Java.
When reading catalina.out this is the clue that the log gives me.
/usr/local/tomcat/work/Catalina/project name here/context of project here/upload_490341a6_12b1d397355_76ce_00000001.tmp
I'm thinking somewhere along the lines (note: this is not an actual method :P )
ServletActionContext.getContext().getSuperHiddenTemporaryCatalog();
The code snippet at the bottom has one flaw.
sourceFile and targetFile points to the same directory at the moment.
I want the sourceFile path to be the temporary tomcat-folder.
Thanks in advance! :D
public String saveImage(File file, String uploadedFileName) {
String path = ServletActionContext.getServletContext().getRealPath("images");
System.out.println(path);
String fullFileName = path + "/" + uploadedFileName;
System.out.println(fullFileName);
boolean successful = false;
try {
File sourceFile = new File(fullFileName);
File targetFile = new File(path + "/" + uploadedFileName);
InputStream in = new FileInputStream(sourceFile);
OutputStream out = new FileOutputStream(targetFile);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (Exception e) {
successful = false;
e.printStackTrace();
}
if (successful) {
return "context of project/images/" + uploadedFileName;
} else {
return "";
}
}
File tempDir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
should give you access to your temporary directory in Tomcat. It would be strange if you could not at least read files from there.
The code I'm using works when I deploy my project on my local machine but stops working when I deploy it live. I've found out that it has something to with my permissions in java.policy.
Yes. This is an example of a Java security sandbox.
What I need to find out is how do I get access to the folder in which Tomcat stores the temporary file using Java.
You cannot circumvent the security sandbox (modulo some unpatched bug in your JVM). What you need to do is change the "java.policy" settings so that your webapp has permission copy the file to where it needs to be copied. You may need to discuss this with whoever has done the security design, etc for your production Tomcats.