I read here that one should not save the file in the server anyway as it is not portable, transactional and requires external parameters. However, given that I need a tmp solution for tomcat (7) and that I have (relative) control over the server machine I want to know :
What is the best place to save the file ? Should I save it in /WEB-INF/uploads (advised against here) or someplace under $CATALINA_BASE (see here) or ... ? The JavaEE 6 tutorial gets the path from the user (:wtf:). NB : The file should not be downloadable by any means.
Should I set up a config parameter as detailed here ? I'd appreciate some code (I'd rather give it a relative path - so it is at least Tomcat portable) - Part.write() looks promising - but apparently needs a absolute path
I'd be interested in an exposition of the disadvantages of this approach vs a database/JCR repository one
Unfortunately the FileServlet by #BalusC concentrates on downloading files, while his answer on uploading files skips the part on where to save the file.
A solution easily convertible to use a DB or a JCR implementation (like jackrabbit) would be preferable.
Store it anywhere in an accessible location except of the IDE's project folder aka the server's deploy folder, for reasons mentioned in the answer to Uploaded image only available after refreshing the page:
Changes in the IDE's project folder does not immediately get reflected in the server's work folder. There's kind of a background job in the IDE which takes care that the server's work folder get synced with last updates (this is in IDE terms called "publishing"). This is the main cause of the problem you're seeing.
In real world code there are circumstances where storing uploaded files in the webapp's deploy folder will not work at all. Some servers do (either by default or by configuration) not expand the deployed WAR file into the local disk file system, but instead fully in the memory. You can't create new files in the memory without basically editing the deployed WAR file and redeploying it.
Even when the server expands the deployed WAR file into the local disk file system, all newly created files will get lost on a redeploy or even a simple restart, simply because those new files are not part of the original WAR file.
It really doesn't matter to me or anyone else where exactly on the local disk file system it will be saved, as long as you do not ever use getRealPath() method. Using that method is in any case alarming.
The path to the storage location can in turn be definied in many ways. You have to do it all by yourself. Perhaps this is where your confusion is caused because you somehow expected that the server does that all automagically. Please note that #MultipartConfig(location) does not specify the final upload destination, but the temporary storage location for the case file size exceeds memory storage threshold.
So, the path to the final storage location can be definied in either of the following ways:
Hardcoded:
File uploads = new File("/path/to/uploads");
Environment variable via SET UPLOAD_LOCATION=/path/to/uploads:
File uploads = new File(System.getenv("UPLOAD_LOCATION"));
VM argument during server startup via -Dupload.location="/path/to/uploads":
File uploads = new File(System.getProperty("upload.location"));
*.properties file entry as upload.location=/path/to/uploads:
File uploads = new File(properties.getProperty("upload.location"));
web.xml <context-param> with name upload.location and value /path/to/uploads:
File uploads = new File(getServletContext().getInitParameter("upload.location"));
If any, use the server-provided location, e.g. in JBoss AS/WildFly:
File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");
Either way, you can easily reference and save the file as follows:
File file = new File(uploads, "somefilename.ext");
try (InputStream input = part.getInputStream()) {
Files.copy(input, file.toPath());
}
Or, when you want to autogenerate an unique file name to prevent users from overwriting existing files with coincidentally the same name:
File file = File.createTempFile("somefilename-", ".ext", uploads);
try (InputStream input = part.getInputStream()) {
Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
How to obtain part in JSP/Servlet is answered in How to upload files to server using JSP/Servlet? and how to obtain part in JSF is answered in How to upload file using JSF 2.2 <h:inputFile>? Where is the saved File?
Note: do not use Part#write() as it interprets the path relative to the temporary storage location defined in #MultipartConfig(location). Also make absolutely sure that you aren't corrupting binary files such as PDF files or image files by converting bytes to characters during reading/writing by incorrectly using a Reader/Writer instead of InputStream/OutputStream.
See also:
How to save uploaded file in JSF (JSF-targeted, but the principle is pretty much the same)
Simplest way to serve static data from outside the application server in a Java web application (in case you want to serve it back)
How to save generated file temporarily in servlet based web application
I post my final way of doing it based on the accepted answer:
#SuppressWarnings("serial")
#WebServlet("/")
#MultipartConfig
public final class DataCollectionServlet extends Controller {
private static final String UPLOAD_LOCATION_PROPERTY_KEY="upload.location";
private String uploadsDirName;
#Override
public void init() throws ServletException {
super.init();
uploadsDirName = property(UPLOAD_LOCATION_PROPERTY_KEY);
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// ...
}
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Collection<Part> parts = req.getParts();
for (Part part : parts) {
File save = new File(uploadsDirName, getFilename(part) + "_"
+ System.currentTimeMillis());
final String absolutePath = save.getAbsolutePath();
log.debug(absolutePath);
part.write(absolutePath);
sc.getRequestDispatcher(DATA_COLLECTION_JSP).forward(req, resp);
}
}
// helpers
private static String getFilename(Part part) {
// courtesy of BalusC : http://stackoverflow.com/a/2424824/281545
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String filename = cd.substring(cd.indexOf('=') + 1).trim()
.replace("\"", "");
return filename.substring(filename.lastIndexOf('/') + 1)
.substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
}
}
return null;
}
}
where :
#SuppressWarnings("serial")
class Controller extends HttpServlet {
static final String DATA_COLLECTION_JSP="/WEB-INF/jsp/data_collection.jsp";
static ServletContext sc;
Logger log;
// private
// "/WEB-INF/app.properties" also works...
private static final String PROPERTIES_PATH = "WEB-INF/app.properties";
private Properties properties;
#Override
public void init() throws ServletException {
super.init();
// synchronize !
if (sc == null) sc = getServletContext();
log = LoggerFactory.getLogger(this.getClass());
try {
loadProperties();
} catch (IOException e) {
throw new RuntimeException("Can't load properties file", e);
}
}
private void loadProperties() throws IOException {
try(InputStream is= sc.getResourceAsStream(PROPERTIES_PATH)) {
if (is == null)
throw new RuntimeException("Can't locate properties file");
properties = new Properties();
properties.load(is);
}
}
String property(final String key) {
return properties.getProperty(key);
}
}
and the /WEB-INF/app.properties :
upload.location=C:/_/
HTH and if you find a bug let me know
Related
I'm moving an existing ASP.NET/MVC app to aspnet core, and there's a bit where I'm not sure of the cleanest solution.
The issue is that we have a shared view that is called from a number of controllers. There's a chunk of html that is loaded from a file by the controller, and then is inserted into the page by the view using #Html.Raw().
The existing code in .NET Framework is using an extension method on Controller to get the contents of the file:
public static class ControllerExtension
{
public static string GetContents(this Controller controller, string path)
{
var filepath = controller.Server.MapPath(path);
var contents = System.IO.File.ReadAllText(filepath);
return contents;
}
}
This, of course, does not work in aspnet core. There is no Server.MapPath().
Googling around I found that the recommended approach is to use IWebHostEnvironment. I could do that - inject it into my controller, and pass it to my GetContents() extension, but that's starting to have a bit of a whiff about it.
So I'm wondering, is there some other mechanism for doing the basic task?
Assume I have a number of files in a directory under wwwroot, each containing plain html.
What would be the cleanest way to have a controller include the contents of one of these files in a .cshtml view?
I've used the WebRootPath property of the environment to get to wwwroot and use relative paths from there. Example:
public class MyController
{
private readonly IWebHostEnvironment environment;
public MyController(IWebHostEnvironment environment)
{
this.environment = environment ?? throw new ArgumentNullException(nameof(environment));
}
public IActionResult GetContents(string path)
{
var contentPath = Path.Combine(environment.WebRootPath, path);
var content = System.IO.File.ReadAllText(contentPath);
// Do something with content and return
}
}
You would of course want more checks to validate the user-provided path before attempting to read the file. Also, would likely make this async unless you cache the content and normally serve the memory-cached data.
I read here that one should not save the file in the server anyway as it is not portable, transactional and requires external parameters. However, given that I need a tmp solution for tomcat (7) and that I have (relative) control over the server machine I want to know :
What is the best place to save the file ? Should I save it in /WEB-INF/uploads (advised against here) or someplace under $CATALINA_BASE (see here) or ... ? The JavaEE 6 tutorial gets the path from the user (:wtf:). NB : The file should not be downloadable by any means.
Should I set up a config parameter as detailed here ? I'd appreciate some code (I'd rather give it a relative path - so it is at least Tomcat portable) - Part.write() looks promising - but apparently needs a absolute path
I'd be interested in an exposition of the disadvantages of this approach vs a database/JCR repository one
Unfortunately the FileServlet by #BalusC concentrates on downloading files, while his answer on uploading files skips the part on where to save the file.
A solution easily convertible to use a DB or a JCR implementation (like jackrabbit) would be preferable.
Store it anywhere in an accessible location except of the IDE's project folder aka the server's deploy folder, for reasons mentioned in the answer to Uploaded image only available after refreshing the page:
Changes in the IDE's project folder does not immediately get reflected in the server's work folder. There's kind of a background job in the IDE which takes care that the server's work folder get synced with last updates (this is in IDE terms called "publishing"). This is the main cause of the problem you're seeing.
In real world code there are circumstances where storing uploaded files in the webapp's deploy folder will not work at all. Some servers do (either by default or by configuration) not expand the deployed WAR file into the local disk file system, but instead fully in the memory. You can't create new files in the memory without basically editing the deployed WAR file and redeploying it.
Even when the server expands the deployed WAR file into the local disk file system, all newly created files will get lost on a redeploy or even a simple restart, simply because those new files are not part of the original WAR file.
It really doesn't matter to me or anyone else where exactly on the local disk file system it will be saved, as long as you do not ever use getRealPath() method. Using that method is in any case alarming.
The path to the storage location can in turn be definied in many ways. You have to do it all by yourself. Perhaps this is where your confusion is caused because you somehow expected that the server does that all automagically. Please note that #MultipartConfig(location) does not specify the final upload destination, but the temporary storage location for the case file size exceeds memory storage threshold.
So, the path to the final storage location can be definied in either of the following ways:
Hardcoded:
File uploads = new File("/path/to/uploads");
Environment variable via SET UPLOAD_LOCATION=/path/to/uploads:
File uploads = new File(System.getenv("UPLOAD_LOCATION"));
VM argument during server startup via -Dupload.location="/path/to/uploads":
File uploads = new File(System.getProperty("upload.location"));
*.properties file entry as upload.location=/path/to/uploads:
File uploads = new File(properties.getProperty("upload.location"));
web.xml <context-param> with name upload.location and value /path/to/uploads:
File uploads = new File(getServletContext().getInitParameter("upload.location"));
If any, use the server-provided location, e.g. in JBoss AS/WildFly:
File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");
Either way, you can easily reference and save the file as follows:
File file = new File(uploads, "somefilename.ext");
try (InputStream input = part.getInputStream()) {
Files.copy(input, file.toPath());
}
Or, when you want to autogenerate an unique file name to prevent users from overwriting existing files with coincidentally the same name:
File file = File.createTempFile("somefilename-", ".ext", uploads);
try (InputStream input = part.getInputStream()) {
Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
How to obtain part in JSP/Servlet is answered in How to upload files to server using JSP/Servlet? and how to obtain part in JSF is answered in How to upload file using JSF 2.2 <h:inputFile>? Where is the saved File?
Note: do not use Part#write() as it interprets the path relative to the temporary storage location defined in #MultipartConfig(location). Also make absolutely sure that you aren't corrupting binary files such as PDF files or image files by converting bytes to characters during reading/writing by incorrectly using a Reader/Writer instead of InputStream/OutputStream.
See also:
How to save uploaded file in JSF (JSF-targeted, but the principle is pretty much the same)
Simplest way to serve static data from outside the application server in a Java web application (in case you want to serve it back)
How to save generated file temporarily in servlet based web application
I post my final way of doing it based on the accepted answer:
#SuppressWarnings("serial")
#WebServlet("/")
#MultipartConfig
public final class DataCollectionServlet extends Controller {
private static final String UPLOAD_LOCATION_PROPERTY_KEY="upload.location";
private String uploadsDirName;
#Override
public void init() throws ServletException {
super.init();
uploadsDirName = property(UPLOAD_LOCATION_PROPERTY_KEY);
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// ...
}
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Collection<Part> parts = req.getParts();
for (Part part : parts) {
File save = new File(uploadsDirName, getFilename(part) + "_"
+ System.currentTimeMillis());
final String absolutePath = save.getAbsolutePath();
log.debug(absolutePath);
part.write(absolutePath);
sc.getRequestDispatcher(DATA_COLLECTION_JSP).forward(req, resp);
}
}
// helpers
private static String getFilename(Part part) {
// courtesy of BalusC : http://stackoverflow.com/a/2424824/281545
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String filename = cd.substring(cd.indexOf('=') + 1).trim()
.replace("\"", "");
return filename.substring(filename.lastIndexOf('/') + 1)
.substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
}
}
return null;
}
}
where :
#SuppressWarnings("serial")
class Controller extends HttpServlet {
static final String DATA_COLLECTION_JSP="/WEB-INF/jsp/data_collection.jsp";
static ServletContext sc;
Logger log;
// private
// "/WEB-INF/app.properties" also works...
private static final String PROPERTIES_PATH = "WEB-INF/app.properties";
private Properties properties;
#Override
public void init() throws ServletException {
super.init();
// synchronize !
if (sc == null) sc = getServletContext();
log = LoggerFactory.getLogger(this.getClass());
try {
loadProperties();
} catch (IOException e) {
throw new RuntimeException("Can't load properties file", e);
}
}
private void loadProperties() throws IOException {
try(InputStream is= sc.getResourceAsStream(PROPERTIES_PATH)) {
if (is == null)
throw new RuntimeException("Can't locate properties file");
properties = new Properties();
properties.load(is);
}
}
String property(final String key) {
return properties.getProperty(key);
}
}
and the /WEB-INF/app.properties :
upload.location=C:/_/
HTH and if you find a bug let me know
I am building a Windows 8 application using sql-net and mvvmcross for data access to a sqlite database. This would be applicable to any Win-8 or Win-Phone app.
I need to install an existing sqlite file on app start.
When using the connection you use syntax such as this
public FlashCardManager(ISQLiteConnectionFactory factory, IMvxMessenger messenger)
{
_messenger = messenger;
_connection = factory.Create("Dictionary.sqlite");
_connection.CreateTable<FlashCardSet>();
_connection.CreateTable<FlashCard>();
}
public void CreateCard(FlashCard flashCard)
{
_connection.Insert(flashCard);
}
That connection creates a file in: C:\Users\USER\AppData\Local\Packages\793fd702-171e-474f-ab3b-d9067c58709b_ka9b83fa3fse2\LocalState
My application uses an existing sqlite database file that I have created. I need to place it in this folder when the application is installed. How would I go about doing this?
Thanks,
JH
Make sure you have the database file you want your app to start off with in one of your apps folders (as in the folders visible in visual studios solution explorer). For this example I'll call this folder "Assets"
All you need to do then is copy this file to the LocalState folder the first time your app runs. This can be done in App.xaml.cs
private async void InitializeAppEnvironment()
{
try
{
if (!(await AppHelper.ExistsInStorageFolder(AppHelper.localFolder, dbName)))
{
StorageFile defaultDb = await AppHelper.installedLocation.GetFileAsync("Assets\\" + dbName);
await defaultDb.CopyAsync(AppHelper.localFolder);
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e);
}
}
I made an AppHelper class to simplify accessing the app data folders, here's the parts I used above:
static class AppHelper
{
public static StorageFolder installedLocation = Windows.ApplicationModel.Package.Current.InstalledLocation;
public static StorageFolder localFolder = ApplicationData.Current.LocalFolder;
public static async Task<bool> ExistsInStorageFolder(this StorageFolder folder, string fileName)
{
try
{
await folder.GetFileAsync(fileName);
return true;
}
catch (FileNotFoundException)
{
return false;
}
}
}
For a more detailed response on MVVM cross I found the current discussion about cross platform file placement in this discussion: Link
The current thought is that you have to inject platform specific code for this sort of functionality.
I have built a dynamic web project MusicStore in Eclipse, and I want to access an xml file located in WebContent/_res/Songs.xml. The method I used to access it in a regular Java class[not a servlet] is:
URL url = this.getClass().getClassLoader().getResource("/");
String filePath = url.getFile();
String webAppLoc=new File(filePath).getParentFile().getParent();
String resLoc=webAppLoc+"/_res/Songs.xml";
It seems to me that this is very cumbersome. Is there a better, more efficient way? thanks!
Here you can try this to access file
public class Test {
public static void main(String[] args){
//This gives me path for the current working directory.
String fileName = System.getProperty("user.dir");
//This gives me path for the file that is residing in folder called "xml_tutorial".
File file = new File(fileName + "../../xml_tutorial/sample.xlsx" );
System.out.println(file.getCanonicalPath());
}
}
my requirement is as follows:
INFO: icefaces upload component, uploads the files to relative folder and creates for each user a sub-directory in that folder with the sessionId.
Requirement: at the sessionDestroyed for each user, i want to get the real path, to delete current user folder.
i know how to get the real path with JSF as follows:
ServletContext ctx = (ServletContext) FacesContext.getCurrentInstance()
.getExternalContext().getContext();
String deploymentDirectoryPath = ctx.getRealPath("/");
Problem: if you try to get the real path in sessionDestroyed you will get null pointer exception, so i was wondering if there's a way to initialize the variable deploymentDirectoryPath in the listener so i can use it in sessionDestroyed method, or maybe initialize the real path variable on application startup and use it here ?
please advise how to solve this issue.
Even though you haven't posted the actual code that relates to the problem, the following gives me the real path:
public class MySessListener implements HttpSessionListener {
#Override
public void sessionCreated(final HttpSessionEvent se) {
System.out.println(Thread.currentThread().getStackTrace()[1]);
new Timer().schedule(new TimerTask() {
#Override
public void run() {
HttpSession sess = se.getSession();
sess.invalidate();
}
}, 10000);
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println(Thread.currentThread().getStackTrace()[1]);
String realPath = se.getSession().getServletContext().getRealPath("/");
System.out.println("realPath: " + realPath);
}
}
Output
INFO: com.bhesh.demo.web.listener.MySessListener.sessionCreated(MySessListener.java:13)
INFO: com.bhesh.demo.web.listener.MySessListener.sessionDestroyed(MySessListener.java:26)
INFO: realPath: C:\Documents and Settings\Bhesh\My Documents\NetBeansProjects\JsfMessageList\build\web\
Based on BalusC sound advice here and elsewhere one could write a general purpose function like this:
String getPath(){
ExternalContext tmpEC;
tmpEC = FacesContext.getCurrentInstance().getExternalContext();
String realPath=tmpEC.getRealPath("/");
return realPath;
}
You can get the real path as follows:-
FacesContext.getCurrentInstance().getExternalContext().getRealPath("/");
EDIT:-
As mentioned by BalusC in one of his answers, you should think twice before using getRealPath("/") because if one has not chose to expand the war file then, getRealPath("/") might return null.
Use getExternalContext.getResourceAsStream instead. as per, docs, It is valid to call this method during application startup or shutdown.