I'm running a Spring REST application inside a docker container. I have a function inside a Spring controller for saving images and a function for reading them. The function for saving works properly but I have an issue with the function for reading them:
public byte[] getByteArray(String fileName) {
try {
File f = new File("/upload/" + fileName);
return Files.readAllBytes(f.toPath());
} catch (IOException e) {
e.printStackTrace(); // this is for testing
return null;
}
}
However after I use the above function I get this error java.nio.file.NoSuchFileException: /upload/test.png. I checked and this file exists in this directory. What could be the reason Java can't see this file?
Most likely your /upload directory is not accessible to the java process. directories have access rights, an owner, and a group. There is one set of rights for the owner, one for the group, and one for the rest.
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 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
I am trying to only create a file if an older version doesn't exist.
This is what I have that I believe is overwriting it:
warningsFile = new File(plugin.getDataFolder(), "Warnings.yml");
warnings = YamlConfiguration.loadConfiguration(warningsFile);
This is in a constructor, for a secondary class (not main). Whenever the server is reloaded (this is for a server) it appears to execute new File and overwrite. Is there a better way to go about this?
warningsFile = new File(plugin.getDataFolder(), "Warnings.yml");
if(!warningsFile.exists()) {
try {
warningsFile.createNewFile();
}
catch (Exception e) {
Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.RED + "Couldn't create warnings.yml. Error: " + e.getMessage());
}
}
warnings = YamlConfiguration.loadConfiguration(warningsFile);
First, we instantiate an instance of File. If the file doesn't exist,
we try to create it.
If that fails, we tell the console.
Finally, we
load the YAML configuration from that file.
This video I made might help:
http://www.youtube.com/watch?feature=player_embedded&v=_Rntj7r8jTQ
(Knowing of this server implementation)
You can simply call:
plugin.saveResource("warnings.yml", false);
This will load a saved warnings.yml from inside your jarfile (you'll need to put one there), and the false specifies to not overwrite the file.
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/*.
I'm new to Spring MVC. I created a new spring controller method that takes an fileoutputstream and writes xml to it as follows:
#RequestMapping(value = "/xml", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
public final void getMappingsAsXML(HttpServletResponse response) {
Customer customer = getCustomerMappings();
try {
// Generates xml file
xmlHelper.save(customer, new StreamResult(new FileOutputStream("/WEB-INF/content/customermapping.xml")));
// print to browser
// read generated file and write to response outputsteam
} catch (XmlMappingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
However, the above code throws the below exception:
java.io.FileNotFoundException: /WEB-INF/content/customermapping.xml (No such file or directory)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(FileOutputStream.java:194)
at java.io.FileOutputStream.<init>(FileOutputStream.java:84)
The content directory already exists in the WEB-INF folder. Also, are there any better ways to write a file to browser in spring?
[BTW, I'm using maven.]
By using a path of /WEB-INF/content/customermapping.xml you are telling Java to write a file named customermapping.xml to the WEB-INF/content directory at the root of the drive. This doesn't sound like what you want.
Try removing the leading /.
However it's probably a bad idea to write to files within the same directory hosting your web application, as some servlet containers like Tomcat simply remove the entire directory when you undeploy the application (with the default settings). Much better to write to a directory external to the webapp.