자바로 파일다운로를 하는 경우

파일명에 한글이 있거나 공백, 가로등 특수문자가 있는경우

브라우저 별로 참 다양한 파일명으로 다운로드 된다.


각 브라우저별로 파일을 다운로드 할때 적당한 인코딩을 하여

다운로드 받는 파일명 처리를 해주어야 한다.


아래 소스에서 각 브라우저 별로

다운로드 되는 파일명을 적절하게 인코딩하여 내려준다.


@RequestMapping( value = "/download.do" )

public void filedown( @RequestParam Map<String, Object> param, HttpServletRequest request,

HttpServletResponse response ) throws Exception {


File file = (File) param.get( "downloadFile" );

String displayFileName = String.valueOf( param.get( "displayFileName" ) );

String encodedFilename = "";

response.setContentType( "application/download; UTF-8" );

response.setContentLength( (int) file.length() );


String header = request.getHeader( "User-Agent" );

if ( header.indexOf( "MSIE" ) > -1 ) {

encodedFilename = URLEncoder.encode( displayFileName, "UTF-8" ).replaceAll( "\\+", "%20" );

}

else if ( header.indexOf( "Trident" ) > -1 ) { 

encodedFilename = URLEncoder.encode( displayFileName, "UTF-8" ).replaceAll( "\\+", "%20" );

}

else if ( header.indexOf( "Chrome" ) > -1 ) {

StringBuffer sb = new StringBuffer();

for ( int i = 0; i < displayFileName.length(); i++ ) {

char c = displayFileName.charAt( i );

if ( c > '~' ) {

sb.append( URLEncoder.encode( "" + c, "UTF-8" ) );

}

else {

sb.append( c );

}

}

encodedFilename = sb.toString();

}

else if ( header.indexOf( "Opera" ) > -1 ) {

encodedFilename = "\"" + new String( displayFileName.getBytes( "UTF-8" ), "8859_1" ) + "\"";

}

else if ( header.indexOf( "Safari" ) > -1 ) {

encodedFilename = "\"" + new String( displayFileName.getBytes( "UTF-8" ), "8859_1" ) + "\"";

encodedFilename = URLDecoder.decode( encodedFilename );

}else{

encodedFilename = "\"" + new String( displayFileName.getBytes( "UTF-8" ), "8859_1" ) + "\"";

encodedFilename = URLDecoder.decode( encodedFilename );

}

response.setHeader( "Content-Disposition", "attachment; filename=\"" + encodedFilename + "\";" );

response.setHeader( "Content-Transfer-Encoding", "binary" );


OutputStream out = response.getOutputStream();

FileInputStream fis = null;


try {


fis = new FileInputStream( file );

FileCopyUtils.copy( fis, out );

}

catch ( FileNotFoundException e ) {

if ( LOGGER.isErrorEnabled() ) {

LOGGER.error( e.getMessage(), e );

}

throw new KpxBizException( messageSource, "ERROR.0104" );

}

finally {

IOUtils.closeQuietly( fis );

}


out.flush();

}



String mailProtocol = "smtp";


String mailHost = "smtp.gmail.com";


String mailPort = "587";


String mailId = "abc@gmail.com"; // 구글계정


String mailPassword = "abc"; // 구글계정 비밀번호 



String fromName = "보내는사람이름";


String fromEmail = "abc@gmail.com"; // 보내는 사람 메일


String toName = "받는사람이름";


String toEmail = "abc@daum.net"; // 받는사람메일


String mailTitle = "메일 타이틀입니다.";


String mailContents = "메일 내용입니다.";


String debugMode = "false";


String authMode = "true";



try {



boolean debug = Boolean.valueOf(debugMode).booleanValue();




Properties mailProps = new Properties();


mailProps.put("mail.smtp.starttls.enable", "true");


mailProps.setProperty("mail.transport.protocol", mailProtocol); 


mailProps.put("mail.debug", debugMode);


mailProps.put("mail.smtp.host", mailHost);


mailProps.put("mail.smtp.port", mailPort);


mailProps.put("mail.smtp.connectiontimeout", "5000");


mailProps.put("mail.smtp.timeout", "5000");  


mailProps.put("mail.smtp.auth", authMode);



Session msgSession = null;


if(authMode.equals("true")) {


        Authenticator auth = new MyAuthentication(mailId, mailPassword);


msgSession = Session.getInstance(mailProps, auth);


} else {


msgSession = Session.getInstance(mailProps, null); 


}



msgSession.setDebug(debug);



MimeMessage msg = new MimeMessage(msgSession);


msg.setFrom(new InternetAddress(fromEmail, fromName));


msg.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail, toName));


msg.setSubject(mailTitle);


msg.setContent(mailContents, "text/html; charset=euc-kr");



// 스태틱함수로 직접 보내지 않고 객체를 이용해서 보내고 객체를 닫아준다. 


Transport t = msgSession.getTransport(mailProtocol);


try {


t.connect();


t.sendMessage(msg, msg.getAllRecipients());


} finally {


  t.close();


}


  


} catch(Exception e) {


e.printStackTrace();


}


}





위 소스로 메일 발송시

hanmail, daum 메일만 메일제목에 _(언더바)가 나오는 현상이 발생했다.

원인은

msg.setSubject(mailTitle);

의 설정에서 좀더 엄격하게 해 주어야 하는데 그걸 한메일 서버가 인식을 제대로 못하는거 같다.


소스를 아래와 같이 수정하니 정상적으로 발송됐다.


msg.setSubject( MimeUtility.encodeText( mailTitle, "utf-8", "B" ) );





자바로 메일 보내기



전자정부 프레임워크 3.7 버전에서 오류 메세지

실행을 하니 아래와 같은 오류가 발생


이유는 자바9버전과 맞지 않아서 생기는 문제

자바8을 사용하거나

eclipse.ini 파일에

--launcher.appendVmargs

-vm

C:\Program Files\Java\jdk-9\bin\javaw.exe

-vmargs

-Dosgi.requiredJavaVersion=1.8

--add-modules=ALL-SYSTEM

같은 설정을 추가하면 됨



!SESSION 2018-02-20 13:59:38.072 -----------------------------------------------

eclipse.buildId=4.6.3.M20170301-0400

java.version=9.0.1

java.vendor=Oracle Corporation

BootLoader constants: OS=win32, ARCH=x86_64, WS=win32, NL=ko_KR

Framework arguments:  -product org.eclipse.epp.package.jee.product

Command-line arguments:  -os win32 -ws win32 -arch x86_64 -product org.eclipse.epp.package.jee.product


!ENTRY org.eclipse.osgi 4 0 2018-02-20 14:00:17.787

!MESSAGE Application error

!STACK 1

org.eclipse.e4.core.di.InjectionException: java.lang.NoClassDefFoundError: javax/annotation/PostConstruct

at org.eclipse.e4.core.internal.di.InjectorImpl.internalMake(InjectorImpl.java:386)

at org.eclipse.e4.core.internal.di.InjectorImpl.make(InjectorImpl.java:294)

at org.eclipse.e4.core.contexts.ContextInjectionFactory.make(ContextInjectionFactory.java:162)

at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createDefaultHeadlessContext(E4Application.java:490)

at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createDefaultContext(E4Application.java:504)

at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createE4Workbench(E4Application.java:203)

at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:632)

at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:336)

at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:610)

at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:148)

at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:138)

at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)

at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134)

at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)

at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:388)

at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:243)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:564)

at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:673)

at org.eclipse.equinox.launcher.Main.basicRun(Main.java:610)

at org.eclipse.equinox.launcher.Main.run(Main.java:1519)

Caused by: java.lang.NoClassDefFoundError: javax/annotation/PostConstruct

at org.eclipse.e4.core.internal.di.InjectorImpl.inject(InjectorImpl.java:151)

at org.eclipse.e4.core.internal.di.InjectorImpl.internalMake(InjectorImpl.java:375)

... 22 more

Caused by: java.lang.ClassNotFoundException: javax.annotation.PostConstruct cannot be found by org.eclipse.e4.core.di_1.6.1.v20160712-0927

at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:410)

at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:372)

at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:364)

at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:161)

at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)

... 24 more

mybatis foreach 문의 기본 문형은


<select id="select" parameterType="hashMap" resultType="hashMap">

SELECT *

FROM USER

WHERE ID IN

<foreach collection="arrId" item="id"  open="(" close=")" separator=",">

#{id}

</foreach>

</select>


실제 실행되는 쿼리는 

SELECT *

FROM USER

WHERE ID IN

( 'a', 'b', 'c')


클라이언트에서 JSON 형식으로 파라미터가 날라와서

리스트안에 맵이 있는 경우는

자바에서 사용하듯이 사용하면 된다.


<select id="select" parameterType="hashMap" resultType="hashMap">

SELECT *

FROM USER

WHERE ID IN

<foreach collection="list" item="map"  open="(" close=")" separator=",">

#{map.id}

</foreach>

</select>

java나 jsp파일에서 날짜를 형식대로 출력하는 소스입니다.


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@page import="java.util.Date" %>

<%@page import="java.text.SimpleDateFormat" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" lang="ko" xml:lang="ko">

<head>

<title>TIME</title>

</head>

<body>

<%

Date now = new Date();

%>

<%=now %><br>

<%

SimpleDateFormat sf = new SimpleDateFormat("yyyyMMddHHmmss");

String today = sf.format(now);

%>

<%=today %><br>

<%

sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

today = sf.format(now);

%>

<%=today %><br>

<%

sf = new SimpleDateFormat("yyyy년MM월dd일 E요일 a hh:mm:ss");

today = sf.format(now);

%>

<%=today %><br>

</body>

</html>


스프링 프로젝트를 생성하고 샘플 코드를 실행하면 한글이 깨져서 나옵니다.




아래와 같이 한글이 깨지네요.



바로 UTF-8 설정이 안되어 그런건데요.


해결 방법은 

1. JSP파일에 인코딩 정보를 추가해 줍니다.


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 

<%@ page session="false" %> 

<html> 

<head> 

<title>Home</title> 

</head> 

<body> 

<h1> 

Hello world!   

</h1> 


<P>  The time on the server is ${serverTime}. </P> 

</body> 

</html>


위 굵은 부분을 추가해 줍니다.


다른 방법으로

2. web.xml 에 인코딩 정보를 추가해 줍니다.


<filter>   

    <filter-name>encodingFilter</filter-name>   

    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>   

    <init-param>   

       <param-name>encoding</param-name>   

       <param-value>UTF-8</param-value>   

    </init-param>   

    <init-param>   

       <param-name>forceEncoding</param-name>   

       <param-value>true</param-value>   

    </init-param>   

</filter>   

<filter-mapping>   

    <filter-name>encodingFilter</filter-name>   

    <url-pattern>/*</url-pattern>   

</filter-mapping>


위 셋팅을 web.xml에 추가합니다.


특히 forceEncoding 부분을 같이 넣어주면 각 JSP파일에 인코딩 부분을 추가하지 않아도 강제 인코딩이 적용됩니다.



스프링이 아닌 단순 웹 프로젝트인 경우


server.xml


<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="utf-8"/>

를 추가하여 한글 URL을 사용할 수 있습니다.




Java was started but returned exit code=13 - 이클립스 실행시 에러



아주 오래된 프로젝트를 변경할 일이 있어 고이고이 간직됐던 프로젝트를 카피해서 실행하는데 에러

해당 이클립스에 맞는 자바를 찾지 못해서 발생하는 에러입니다.


이클립스는 32비트이고 내PC는 64비트인지라..

그래서 32비트용 자바 JDK를 다운받습니다.

기본 폴더에 설치한다면 실행될겁니다.


특정 폴더에 설치하셨다면 이클립스 폴더의 eclipse.ini 파일을 문서 편집기로 

해당 폴더를 설정해 주면 됩니다.

OpenFile 밑에 아래를 추가합니다.


-vm 

D:\eclipse\java\86\jdk1.8.0_131\bin\javaw.exe

붉은색 부분은 설치된 각자의 폴더에 맞게 넣어주면 됩니다.







자바로 메일 보내기..

우선  mail.jar 파일이 필요하다.


https://mvnrepository.com/artifact/javax.mail/mail

메이븐 사이트에서 중간에 버전을 클릭하고 들어가서 Download 링크를 클릭하여 다운받는다.


자바 소스를 코딩한다.


import java.util.Properties;


import javax.mail.Authenticator;

import javax.mail.Message;

import javax.mail.PasswordAuthentication;

import javax.mail.Session;

import javax.mail.Transport;

import javax.mail.internet.InternetAddress;

import javax.mail.internet.MimeMessage;


public class Main {


public static void main(String[] args) {

String mailProtocol = "smtp";

String mailHost = "smtp.gmail.com";

String mailPort = "587";

String mailId = "abc@gmail.com"; // 구글계정

String mailPassword = "abc"; // 구글계정 비밀번호 

String fromName = "보내는사람이름";

String fromEmail = "abc@gmail.com"; // 보내는 사람 메일

String toName = "받는사람이름";

String toEmail = "abc@daum.net"; // 받는사람메일

String mailTitle = "메일 타이틀입니다.";

String mailContents = "메일 내용입니다.";

String debugMode = "false";

String authMode = "true";

try {

boolean debug = Boolean.valueOf(debugMode).booleanValue();


Properties mailProps = new Properties();

mailProps.put("mail.smtp.starttls.enable", "true");

mailProps.setProperty("mail.transport.protocol", mailProtocol); 

mailProps.put("mail.debug", debugMode);

mailProps.put("mail.smtp.host", mailHost);

mailProps.put("mail.smtp.port", mailPort);

mailProps.put("mail.smtp.connectiontimeout", "5000");

mailProps.put("mail.smtp.timeout", "5000");  

mailProps.put("mail.smtp.auth", authMode);

Session msgSession = null;

if(authMode.equals("true")) {

       Authenticator auth = new MyAuthentication(mailId, mailPassword);

msgSession = Session.getInstance(mailProps, auth);

} else {

msgSession = Session.getInstance(mailProps, null); 

}

msgSession.setDebug(debug);

MimeMessage msg = new MimeMessage(msgSession);

msg.setFrom(new InternetAddress(fromEmail, fromName));

msg.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail, toName));

msg.setSubject(mailTitle);

msg.setContent(mailContents, "text/html; charset=euc-kr");

// 스태틱함수로 직접 보내지 않고 객체를 이용해서 보내고 객체를 닫아준다. 

Transport t = msgSession.getTransport(mailProtocol);

try {

t.connect();

t.sendMessage(msg, msg.getAllRecipients());

} finally {

 t.close();

}

 

} catch(Exception e) {

e.printStackTrace();

}

}


}


class MyAuthentication extends Authenticator {

    PasswordAuthentication pa;

    public MyAuthentication(String mailId, String mailPass) {

        pa = new PasswordAuthentication(mailId, mailPass);  

    }

    public PasswordAuthentication getPasswordAuthentication() {

        return pa;

    }

}



위 소스를 이용하여 메일을 발송한다.

그런데 gmail 설정에서 권한이 없는 경우

javax.mail.AuthenticationFailedException
at javax.mail.Service.connect(Service.java:319)
at javax.mail.Service.connect(Service.java:169)
at javax.mail.Service.connect(Service.java:118)
at Main.main(Main.java:64)

와 같은 에러가 발생한다.


구글 계정관리에 들어가서 
가장 아래부분에 있는
보안 수준이 낮은 앱 허용을 사용으로 세팅해 주고 다시 발송하면 발송이 될 것이다.






JSP에서 현재 경로를 가져오는 것은 

JSP스크립트를 사용하는 경우 <%=request.getRequestURL() %>

contextpath 는 <%=request.getContextPath()  %> 식으로 가져왔다.


하지만 요즘은 스크립트를 JSP에서 사용하지 않으므로 표현식(el)로 가져와야 한다.

${pageContext.request.requestURL}

${pageContext.request.requestURI}

${pageContext.request.contextPath}

이런식으로 가져오면 된다.


이걸 조합해 보면 

${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/

이런 결과를 얻을 수도 있다.


<a href="${pageContext.request.contextPath}/a/b/c/d.do">이동할려는 페이지</a>

위와 같은 코딩에서도 콘텍스트패스가 변경되도 JSP 소스를 변경하지 않아도 된다.


물론 이런 경우는 jstl core의 url 을 이용하는게 더 좋다.

<a href="<c:url value="/a/b/c/d.do" />">이동할려는 페이지</a>


한편 MVC모델에서 pageContext.request.requestURL를 이용하여 경로를 가져오는 경우

WEB-INF의 실제 JSP경로를 가져오게 된다.


표현식으로 현재 URL을 가져오고 싶은 경우는

${requestScope["javax.servlet.forward.request_uri"]}

같이 가져오면 된다.




전자정부표준프레임워크를 설치하고

템플릿 프로젝트를 임포트했다면 기본적으로 ibatis설정으로 되었을 것이다.

그리고 로그는 log4j에 의해서 로그가 남는다.


log4j2.xml 파일을 찾아

<logger name="java.sql.Connection" level="INFO" additivity="false">

<appender-ref ref="console" />

</logger>

에서 INFO를 DEBUG로 변경하면 로그를 볼 수 있다.


또한 java.sql 객체의 각 항목별로 적용하려면 각 항목별 아래 소스를 추가하면 된다.


<logger name="java.sql.Connection" level="DEBUG" additivity="false">

<appender-ref ref="console" />

</logger>

<logger name="java.sql.PreparedStatement" level="DEBUG" additivity="false">

<appender-ref ref="console" />

</logger>

<logger name="java.sql.Statement" level="DEBUG" additivity="false">

<appender-ref ref="console" />

</logger>

<logger name="java.sql.ResultSet" level="DEBUG" additivity="false">

<appender-ref ref="console" />

</logger>


로그 결과물은 다음과 같이 보여진다.



쿼리와 쿼리에 들어가는 파라미터

그리고 쿼리 결과까지

쿼리 결과를 보고 싶지 않다면 java.sql.ResultSet 부분을 지우면 된다.


그런데 문제가 있다.

쿼리는 preparestatement 상태의 쿼리를 보여준다. 즉

SELECT NAME FROM USER WHERE ID = ?

위와같이 파라미터로 들어가는 값은 ? 물음표 표시로 보여진다. 여기에 파리미터를 순서로 적용된 쿼리가 실제 실행되는 쿼리가 된다.


파라미터가 한두개면 각 쿼리에 적용해서 디버깅 하겠지만 파라미터가 많아지고 복잡해 진다면 파라미터를 넣어서 실행하기는 쉽지 않다.


그래서 실행되는 쿼리 자체의 문자열을 받아보고 디버깅을 하고 싶을 것이다.


이런 경우 

log4jdbc-remix 를 사용한다.


우선 pom.xml 에 추가

<dependency>  

<groupId>org.lazyluke</groupId>  

  <artifactId>log4jdbc-remix</artifactId>  

  <version>0.2.4</version>  

</dependency>  


<dependency>  

  <groupId>org.slf4j</groupId>  

  <artifactId>slf4j-log4j12</artifactId>  

  <version>1.6.1</version>  

</dependency>


context-datasource.xml에서 오라클인 경우

    <!-- Oracle -->

    <bean id="dataSourceSpied" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

        <property name="driverClassName" value="${Globals.DriverClassName}"/>

        <property name="url" value="${Globals.Url}" />

        <property name="username" value="${Globals.UserName}"/>

        <property name="password" value="${Globals.Password}"/>

    </bean> 

    <bean id="dataSource-oracle" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">

    <constructor-arg ref="dataSourceSpied" />

    <property name="logFormatter">

    <bean class="net.sf.log4jdbc.tools.Log4JdbcCustomFormatter">

    <property name="loggingType" value="MULTI_LINE" />

    <property name="sqlPrefix" value="SQL:::" />

    </bean>

    </property>

    </bean>


기존의 datasorce 빈 생성 부분아래에

log4jdbc-remix로 연결하는 설정을 추가해 준다.

기존 빈은 참조 아이디로 사용되므로 알맞게 변경해 주면 된다..

추가된 빈은 dataSource-oracle로 아이디를 설정한다.


왜냐하면 윗부분에 dataSource를 alias로 설정하는 부분이 있으므로 상단부분을 수정하지 않을것이면

그대로 사용하는것이 편하다.




+ Recent posts