55890
## 問題点・最終目的

java 初心者です。  
jsp のフォームで受け取った画像ファイルを webサーバにアップロード・保存して、表示させる機能を作っています。  
ネットやAPIを参考に、コードを書いていますが上手くアップロードファイルを作成することが出来ません。  
現時点では、個人の開発環境なので自分のPC内のディレクトリからアップロードするファイルを選択する形ですが、本来は全く別のクライアントから画像を取得するプログラムを作成したいです。

## 開発環境

  * Eclipse（Version: 2019-03 (4.11.0), Tomcat9 , java11）
  * MacOS Mojave Version: 10.14.4

## 発生している例外

タイプ 例外報告

```

    メッセージ /Applications/Eclipse_2019-03.app/Contents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/`upload_server/WEB-INF/upload/_1560801511375/img-landscape536x354.png`
    
```

例外

```

    java.nio.file.`NoSuchFileException: `/Applications/Eclipse_2019-03.app/Contents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/upload_server/WEB-INF/upload/_1560801511375/img-landscape536x354.png
        java.base/sun.nio.fs.`UnixException.translateToIOException`(UnixException.java:92)
        java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
        java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)
        java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:215)
        java.base/java.nio.file.Files.newByteChannel(Files.java:370)
        java.base/java.nio.file.Files.createFile(Files.java:647)
        (以下略)
    
```

## 試した事・その他原因として考えた事 等

・パス・ファイル名の取得方法を変える。  
（はじめ、Part.getSubmittedFileName()を使っていた。）  
・保存先ディレクトリを移動させる。  
（権限の問題？という事でWebContent外へ）  

そもそもパス、シンボリックリンク、サーバの保存先、フォームから得られる情報と形式、など基本的な事を理解できていなかったので、（今も完璧には理解できていない）  
APIなどを参考に、例外に書き出しながらコードを書き換えトライしてみましたが上手くいきません。  
権限の問題なのかとも思い調べて見ましたが（FileSystem等）直接的な解決策になるのかわかりませんでした。

一番不明瞭なのは、

```

    String uploadDir=getServletContext().getRealPath("/WEB-INF/upload");
    
```

で、得られたパスのディレクトリにファイルを作れるのでしょうか。  
...そこに作成するというのがそもそも間違いなのでしょうか。

何か根本的に間違っている点があるのだと思います。

初心者すぎて、ややこしい質問の仕方で申し訳ありません。  
相当時間を費やしていて先に進めないので、どなたかご教示いただければ幸いです。  
どうぞ宜しくお願いします。

以下、コードになります。  
**サーブレット**

```

    package servlet;
    
    import java.io.IOException;
    import java.nio.file.FileAlreadyExistsException;
    (略)
    
    public class FileUpload extends HttpServlet {
        private static final long serialVersionUID = 1L;
    
        public FileUpload() {
            super();
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            String path="/WEB-INF/jsp/fileUpload.jsp";
    
            RequestDispatcher dsp=request.getRequestDispatcher(path);
            dsp.forward(request, response);
    
            response.getWriter().append("Served at: ").append(request.getContextPath());
        }
    
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String title=request.getParameter("title");
        Part part=request.getPart("data");
        String nameadd=(new StringBuilder(title).append("_").append(System.currentTimeMillis()).toString());//オリジナルの名前にする
    
        String filename = new GetFileNameLogic().getFileName(part);
        
        String uploadDir=getServletContext().getRealPath("/WEB-INF/upload");
        Path file=Paths.get(uploadDir,nameadd,filename);
        try {
            Files.createFile(file);
        } catch (UnsupportedOperationException  e) {
            e.printStackTrace();
            throw new UnsupportedOperationException  ("原子的に設定できない属性が配列に含まれます");
        } catch (FileAlreadyExistsException e) {
            e.printStackTrace();
            throw new FileAlreadyExistsException ("同名ファイルがすでに存在します");
        }
    
        FileUploadLogic flogic =new FileUploadLogic ();
        try {
            flogic.execute(part, file);
        } catch (IOException e) {
            e.printStackTrace();
            throw new IOException("ファイル書き込み入出力エラー");
        }
    
        String to="/WEB-INF/result.jsp";
        RequestDispatcher dsp=request.getRequestDispatcher(to);
        dsp.forward(request, response);
    
        doGet(request, response);
      }
    }
    
```

  
**ファイル名を得るBO**

```

    package model;
    
    import javax.servlet.http.Part;
    
    public class GetFileNameLogic{
        public String getFileName(Part part) {
            String name= null;
    
            for (String dispotion : part.getHeader("Content-Disposition").split(";")) {
                if (dispotion.trim().startsWith("filename")) {
                     name = dispotion.substring(dispotion.indexOf("=") + 1).replace("\"", "").trim();
                     name=name.substring(name.lastIndexOf("\\")+1);
    
                     break;
                }
            }
            return name;
        }
    }
    
```

**アップロードのBO**

```

    package model;
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.file.Path;
    
    import javax.servlet.http.Part;
    
    public class FileUploadLogic {
            public void execute(Part part,Path path) throws IOException {
                File file=new File(path.toString());
                try(BufferedInputStream br=new BufferedInputStream(part.getInputStream());
                    BufferedOutputStream bw=new BufferedOutputStream(new FileOutputStream(file))){
                    int len=0;
                    byte[] buff=new byte[(int)part.getSize()];
                    while((len=br.read(buff)) != -1) {
                        bw.write(buff, 0, len);
                    }
                }
            }
    }
    
    
    
      
    
```
CC BY-SA 4.0
2019-06-17T21:44:22.030
55887
2019-06-19T23:01:53.417
2020-06-17T08:14:45.997
-1
34716
question
java jsp servlet
java Servlet&jsp で画像のアップロードファイルの作成が出来ません
```

    String nameadd=(new StringBuilder(title).append("_").append(System.currentTimeMillis()).toString());//オリジナルの名前にする
    
```

これによって生成された名前（`_1560801511375`）のディレクトリーはOS上に存在しないはずです。存在しないディレクトリーにファイルを作成しようとして、`NoSuchFileException`がスローされています。したがって、

```

    Files.createFile(file);
    
```

の前に少なくとも以下のようにディレクトリーをつくっておかないと、正常に動作しないはずです。

```

    Files.createDirectory(Paths.get(uploadDir,nameadd));
    
```
CC BY-SA 4.0
2019-06-18T01:18:38.123
55890
2019-06-18T01:18:38.123
21092
55887
answer
### ServletContext#getRealPath について

> 一番不明瞭なのは、
```

>     String uploadDir=getServletContext().getRealPath("/WEB-INF/upload");
>  
```

>
> で、得られたパスのディレクトリにファイルを作れるのでしょうか。  
>  ...そこに作成するというのがそもそも間違いなのでしょうか。

シンプルな回答としては間違いです。  
作れる場合もありますが作れない場合もあります。作れた場合でもその場所が恒久的に存在するとは限りません。

* * *

`ServletContext#getRealPath` については Servlet仕様書([Servlet 4.0
spec(pdf)](https://javaee.github.io/servlet-
spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf) \- 3.6 Path Translation
Methods)や[API
doc](https://docs.oracle.com/javaee/7/api/javax/servlet/ServletContext.html#getRealPath-
java.lang.String-)で説明されています。

> In situations where the servlet container cannot determine a valid file path
> for these methods, such as when the Web application is executed from an
> archive, on a remote file system not accessible locally, or in a database,
> these methods must return null.

* * *

具体例を挙げると、Tomcatには[`unpackWARs`](http://tomcat.apache.org/tomcat-9.0-doc/deployer-
howto.html#Deploying_on_a_running_Tomcat_server)という設定項目がありこれを`false`に設定すると.warファイルは展開されずにデプロイされます。  
この場合の `ServletContext#getRealPath` 戻り値は上記引用文の通り `null` です。

また別の例として、WildFlyでは.warファイルの自動展開先は一時ディレクトリであり、`ServletContext#getRealPath`の結果は同じアプリケーションでもデプロイのたびに変わる可能性があります。  
アップロードしたファイルが、アプリケーションを再起動すると見つからなくなってしまう、ということです。

* * *

上の話を脇に置いておいたとしても、サーバリソースが配置されている`WEB-
INF`ディレクトリにユーザ生成コンテンツも一緒くたに保存するというのは、運用が煩雑になるというデメリット面が大きいかと思います。

* * *

### 発生している例外について

> 例外
```

>     java.nio.file.`NoSuchFileException:
> `/Applications/Eclipse_2019-03.app/Contents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/upload_server/WEB-
> INF/upload/_1560801511375/img-landscape536x354.png
>
> java.base/sun.nio.fs.`UnixException.translateToIOException`(UnixException.java:92)
>
> java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
>
> java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)
>
> java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:215)
>       java.base/java.nio.file.Files.newByteChannel(Files.java:370)
>       java.base/java.nio.file.Files.createFile(Files.java:647)
>       (以下略)
>  
```

はスタックトレースからわかる通り

```

    String nameadd=(new StringBuilder(title).append("_").append(System.currentTimeMillis()).toString());//オリジナルの名前にする
    
    String filename = new GetFileNameLogic().getFileName(part);
    
    String uploadDir=getServletContext().getRealPath("/WEB-INF/upload");
    Path file=Paths.get(uploadDir,nameadd,filename);
    try {
        Files.createFile(file);
    
```

の `File.createFile` で発生しています。  
例外の発生条件は[API
doc](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/Files.html#createFile\(java.nio.file.Path,java.nio.file.attribute.FileAttribute...\))に記述があり、今回発生している例外  
発生している例外
[`NoSuchFileException`](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/NoSuchFileException.html)
について直接言及はされていませんが、クラス継承ツリーから `NoSuchFileException` は `IOException`
を継承しているのがわかるため、次の記述が該当するであろうことが予想できます:

> `IOException` \- 入出力エラーが発生した場合または親ディレクトリが存在しない場合

要するに、ファイル `img-landscape536x354.png` を作成しようとしたが、このファイルを作成すべき場所(ディレクトリ)
`/Applications/Eclipse_2019-03.app/Contents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/upload_server/WEB-
INF/upload/_1560801511375/` が存在しなかった(可能性が高い)、ということです。

従って、例外に対する直接的な対応としては、あらかじめディレクトリを作成しておく、というものが挙げられます:

```

        Files.createDirectories(file.getParent());
        Files.createFile(file);
    
```

ただし前述の通り、今回の場合ファイルを保存しようとしている場所自体が適切ではありませんので、例外が出なくなったことが根本的に解決されたことを意味するわけではありません。

* * *

### コメントでの追加質問について

>
> ネットで「画像アップデート・ダウンロード、保存」等で調べると、使用するクラスは違うものの、大体上記のような流れのソースコードしか見当たらなかったのですが、これらは全て、一時的なアップロードの作業と言う事でしょうか。だとすれば、「ここからこの画像を恒久的に保存する処理が必要」という解釈で間違い無いでしょうか。

`ServletContext#getRealPath`
の戻り値をファイル出力場所として指定することはたとえそれが一時ファイルであろうと誤りである(更にいうとそんな場所にファイル出力する妥当な理由は無いだろう)、というのが回答になります。  
つまりネットで検索して見つかるそれら全ての実装は(少なくともファイル出力場所指定処理について)間違っている、ということです。  
おそらく誰かが最初に思いついたものを深慮なくコピペ実装した結果かと考えますが、真意は私にはわかりません([オフィシャルのtutorial](https://docs.oracle.com/javaee/7/tutorial/servlets016.htm)がそのような実装を行っているわけでもない)。

  * [Recommended way to save uploaded files in a servlet application - Stack Overflow](https://stackoverflow.com/q/18664579)

でまさに同じ状況での質問と回答がありました。

またこちらの回答には`ServletContext#getRealPath`でない[別の代替実装についても複数案](https://stackoverflow.com/a/18664715/4506703)記述があります。質問の内容からはやや逸れますが、今回の実装を完遂する上で必要な情報かと思います。  
補足すると、現在では[MicroProfile
config](https://microprofile.io/project/eclipse/microprofile-config)というJava
EEで(も)利用可能なコンフィグ読み取りの仕様といくつかの実装が提供されいます(やることは結局前述リンク先回答と同じなのですが)。今回の件だけで用いるのはオーバースペックかもしれませんが、参考として。

> 調べた結果「画像を直接DB保存」「画像は、別の場所に保存しパスのみをDB保存」
> などの方法を知ることができたのですが、具体的にはどのようにするのがベストなのかもご教示いただけると幸いです。

こちらについては、JavaではなくファイルシステムやDBMSの機能性能の話になるかと思いますので、別に質問を立てられるのが良いかと思います。  
また、システムに対する要求にも依存すると考えますので、どういう想定のシステム(例えば利用ユーザ数や取り扱うファイル数)に対する質問なのかも明記したほうが良いでしょう。
CC BY-SA 4.0
2019-06-18T16:19:02.233
55904
2019-06-19T23:01:53.417
2019-06-19T23:01:53.417
2808
2808
55887
answer