diff --git a/src/main/java/cokr/xit/foundation/Downloadable.java b/src/main/java/cokr/xit/foundation/Downloadable.java new file mode 100644 index 0000000..6916674 --- /dev/null +++ b/src/main/java/cokr/xit/foundation/Downloadable.java @@ -0,0 +1,157 @@ +package cokr.xit.foundation; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLConnection; +import java.util.function.Consumer; + +/**다양한 유형의 파일의 다운로드를 지원하기 위한 클래스 + * @author mjkhan + */ +public class Downloadable { + /**지정한 파일을 Downloadable로 변환하여 반환한다. + * @param file 파일 + * @return Downloadable + */ + public static Downloadable create(File file) { + String filename = file.getName(); + return new Downloadable() + .setFilename(filename) + .setContentType(URLConnection.guessContentTypeFromName(filename)) + .setLength(file.length()) + .setWriter(out -> { + try (FileInputStream input = new FileInputStream(file)) { + input.transferTo(out); + } catch (Exception e) { + Assert.runtimeException(e); + } + }); + } + + /**지정한 InputStream을 Downloadable로 변환하여 반환한다. + * 결과로 받은 Downloadable은 파일이름을 지정해야 한다. + * @param inputStream InputStream + * @return Downloadable + */ + public static Downloadable create(InputStream inputStream) { + try { + return new Downloadable() + .setContentType(URLConnection.guessContentTypeFromStream(inputStream)) + .setWriter(out -> { + try (InputStream input = inputStream) { + input.transferTo(out); + } catch (Exception e) { + throw Assert.runtimeException(e); + } + }); + } catch (Exception e) { + throw Assert.runtimeException(e); + } + } + + private String + charset, + filename, + contentType, + disposition; + private Long length; + private Consumer writer; + + /**문자셋을 반환한다. 디폴트는 UTF-8 + * @return 문자셋 + */ + public String getCharset() { + return Assert.ifEmpty(charset, "UTF-8"); + } + + /**문자셋을 설정한다. + * @param charset 문자셋 + * @return 현재 Downloadable + */ + public Downloadable setCharset(String charset) { + this.charset = charset; + return this; + } + + /**파일이름을 반환한다. + * @return 파일이름 + */ + public String getFilename() { + return Assert.notEmpty(filename, "filename"); + } + + /**파일이름을 설정한다. + * @param filename 파일이름 + * @return 현재 Downloadable + */ + public Downloadable setFilename(String filename) { + this.filename = filename; + return this; + } + + /**contentType을 반환한다. 디폴트는 application/octet-stream + * @return contentType + */ + public String getContentType() { + return Assert.ifEmpty(contentType, "application/octet-stream"); + } + + /**contentType을 설정한다. + * @param contentType contentType + * @return 현재 Downloadable + */ + public Downloadable setContentType(String contentType) { + this.contentType = contentType; + return this; + } + + /**disposition을 반환한다. 디폴트는 attachment + * @return disposition + */ + public String getDisposition() { + return Assert.ifEmpty(disposition, "attachment"); + } + + /**disposition을 설정한다. + * @param disposition disposition + * @return 현재 Downloadable + */ + public Downloadable setDisposition(String disposition) { + this.disposition = disposition; + return this; + } + + /**파일길이를 반환한다. + * @return 파일길이 + */ + public Long getLength() { + return length; + } + + /**파일길이를 설정한다. + * @param length 파일길이 + * @return 현재 Downloadable + */ + public Downloadable setLength(Long length) { + this.length = length; + return this; + } + + /**파일데이터를 저장할 함수를 설정한다. + * @param writer 파일데이터를 저장할 함수 + * @return 현재 Downloadable + */ + public Downloadable setWriter(Consumer writer) { + this.writer = writer; + return this; + } + + /**파일데이터를 지정한 스트림에 저장한다. + * @param out OutputStream + */ + public void write(OutputStream out) { + Assert.notEmpty(writer, "writer").accept(out); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/web/AbstractController.java b/src/main/java/cokr/xit/foundation/web/AbstractController.java index 5470035..529fe54 100644 --- a/src/main/java/cokr/xit/foundation/web/AbstractController.java +++ b/src/main/java/cokr/xit/foundation/web/AbstractController.java @@ -1,7 +1,5 @@ package cokr.xit.foundation.web; -import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; import java.net.URLEncoder; import java.util.Collection; @@ -10,7 +8,6 @@ import java.util.Map; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; -import org.springframework.util.FileCopyUtils; import org.springframework.web.servlet.ModelAndView; import com.fasterxml.jackson.core.type.TypeReference; @@ -18,6 +15,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import cokr.xit.foundation.AbstractComponent; import cokr.xit.foundation.Access; +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.Downloadable; import cokr.xit.foundation.component.QueryRequest; import cokr.xit.foundation.data.paging.BoundedList; @@ -218,44 +217,31 @@ public abstract class AbstractController extends AbstractComponent { } } - /**InputStream을 지정한 유형과 이름으로 다운로드 한다. - * @param input 다운로드할 파일의 InputStream - * @param contentType 컨텐트 유형. 지정하지 않으면 application/octet-stream - * @param filename 다운로드됐을 때 사용자가 보게되는 파일 이름 - * @param length 다운로드할 파일의 길이 - * @param disposition 'attachment', 또는 'inline'. 지정하지 않으면 attachment - * @param hresp HttpServletResponse + /**주어진 downloadable로 설정된 데이터를 다운로드한다. + * @param downloadable Downloadable + * @param hresp HttpServletResponse * @throws Exception */ - protected void download(InputStream input, String contentType, String filename, long length, String disposition, HttpServletResponse hresp) throws Exception { - if (input == null) { + protected void download(Downloadable downloadable, HttpServletResponse hresp) throws Exception { + //TODO: DownloadView ->foundation으로 이동 후 render(...) 호출하도록 수정할 것 + if (downloadable == null) { hresp.setStatus(HttpServletResponse.SC_NOT_FOUND); hresp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - hresp.setCharacterEncoding("UTF-8"); - hresp.setContentType(ifEmpty(contentType, "application/octet-stream")); - hresp.setHeader("Content-Disposition", ifEmpty(disposition, "attachment") + "; filename=\"" + URLEncoder.encode(filename, "UTF-8") +"\""); - hresp.setContentLengthLong(length); - FileCopyUtils.copy(input, hresp.getOutputStream()); - } + String filename = downloadable.getFilename(); + if (Assert.isEmpty(filename)) + throw new IllegalArgumentException("Unable to determine the filename"); - /**file을 지정한 유형과 이름으로 다운로드한다. - * @param file 다운로드할 파일 - * @param contentType 컨텐트 유형. 지정하지 않으면 application/octet-stream - * @param filename 다운로드됐을 때 사용자가 보게되는 파일 이름. 지정하지 않으면 file의 이름을 사용한다. - * @param disposition 'attachment', 또는 'inline'. 지정하지 않으면 attachment - * @param hresp HttpServletResponse - * @throws Exception - */ - protected void download(File file, String contentType, String filename, String disposition, HttpServletResponse hresp) throws Exception { - if (file == null) { - download(null, null, null, 0, null, hresp); - return; - } - try (FileInputStream in = new FileInputStream(file)) { - download(in, contentType, ifEmpty(filename, file.getName()), file.length(), disposition, hresp); - } + String charset = downloadable.getCharset(); + hresp.setCharacterEncoding(charset); + hresp.setContentType(downloadable.getContentType()); + hresp.setHeader("Content-Disposition", downloadable.getDisposition() + "; filename=\"" + URLEncoder.encode(filename, charset) +"\""); + Long length = downloadable.getLength(); + if (length != null) + hresp.setContentLengthLong(length.longValue()); + + downloadable.write(hresp.getOutputStream()); } /**임시 디렉토리 경로에 파일 경로를 추가하여 반환한다.