[Android] The version of Gradle you connect to does not support that method

[Android] The version of Gradle you connect to does not support that method

Android 3.4.1에서 발생.

정상적으로 빌드되어야 하는 프로젝트가 The version of Gradle you connect to does not support that method 에러가 나며 진행되지 않았음.

File – Settings – Build, Execution, Deployment – Gradle 에서 [Offline work] 체크 처리 로 해결하였음.

 

[CHROME] 크롬 원격데스크톱

[CHROME] 크롬 원격데스크톱

https://remotedesktop.google.com/

[Visual Studio] 중괄호 설정 (중괄호에 대한 줄 바꿈 옵션)

[Visual Studio] 중괄호 설정 (중괄호에 대한 줄 바꿈 옵션)

public void 메서드명()

{

}

위 형식보다,

public void 메서드명() {

}

위 형식을 선호할 경우 참고할 것.

1. 상단 메뉴의 [도구] – [옵션] 클릭

2. 옵션 창 좌측 트리에서 [텍스트 편집기] – [C#] (또는 원하는 언어) – [서식] – [줄 추가] 클릭

3. “형식의 여는 중괄호를 새 줄에 배치합니다.”, “메서드의 여는 중괄호를 새 줄에 배치합니다.” 등 체크해제

 

[UNITY] 닷지 게임

[UNITY] 닷지 게임

이제민 님의 <레트로의 유니티 게임 프로그래밍 에센스> 1권에 나오는 예제 <닷지>를 그대로 만든 것입니다.

저의 아이디어나 기획은 전혀 포함되어 있지 않습니다.

[SPRING] 스프링에서 css, js 등 404 에러 발생할 경우

[SPRING] 스프링에서 css, js 등 404 에러 발생할 경우

처음 스프링 세팅 후 css, js 등 404 에러 발생하는 경우가 있다.

파일을 webapp 밑에 넣으면, jsp는 매핑되지만 css, js 는 매핑되지 않는다.

web.xml 의 servlet-mapping 을 수정해야 한다.

기존에 jsp만 매핑 규칙이 설정되어 있기 때문이다.

참고로 스프링의 dispatcherServlet 은 default 이름으로 매핑되어 있는 경로 제외하고 @Controller와 매핑한다.

다음은 web.xml 을 수정하는 방법이다.

[AS-IS]

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!– The mappings for the JSP servlet –>
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

[TO-BE]

     <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
        <url-pattern>*.css</url-pattern>
        <url-pattern>*.gif</url-pattern>
        <url-pattern>*.jpg</url-pattern>
        <url-pattern>*.png</url-pattern>

        <url-pattern>*.htm</url-pattern>
        <url-pattern>*.html</url-pattern>​

        <url-pattern>*.xml</url-pattern>

        <url-pattern>*.ico</url-pattern>
    </servlet-mapping>

    <!– The mappings for the JSP servlet –>
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

[VBA] 현재 칸과 바로 아래칸의 병합

[VBA] 현재 칸과 바로 아래칸의 병합

    ‘현재 칸과 바로 아래 칸을 선택
    ‘Range(“A1:A2”).Select
    Range(ActiveCell.Offset(0, 0).Cells, ActiveCell.Offset(1, 0).Cells).Select
   
    ‘셀 병합
    With Selection
        .HorizontalAlignment = xlCenter
        .VerticalAlignment = xlCenter
        .WrapText = False
        .Orientation = 0
        .AddIndent = False
        .IndentLevel = 0
        .ShrinkToFit = False
        .ReadingOrder = xlContext
        .MergeCells = False
    End With
    Selection.Merge
   
    ‘오른쪽으로 한 칸 이동
    ActiveCell.Offset(0, 1).Select

[TOMCAT] 톰캣 내장 웹서버 사용

[TOMCAT] 톰캣 내장 웹서버 사용

기억하기 위해 기록.

개인적으로 만든 어떤 사이트가 있다. 웹서버(아파치) – WAS(톰캣) 구조로 만들어 놓았다.

참고로 정적인 파일(png, jpg 등)은 웹서버인 아파치가 담당하고,

서버 컴파일이 필요한 파일(jsp, class 등)은 WAS가 담당한다.

이 때문에 아파치는 내가 가진 도메인의 30000번 포트를,

톰캣은 9090번 포트를 사용하고 있었다.

웹서버와 톰캣이 둘 다 떠있어도 기존에는 포트 30000번만 사용했다.

30000번(아파치)로 접속했을 때, 정적인 파일을 요청했다면 아파치 단에서 던져주고,
그렇지 않을 경우 9090번 톰캣을 바라보게 하는 아파치 – 톰캣 구조였기 때문이다.

다시 말해 30000번으로 접속해야만 페이지 내용과 그림파일이 다 잘 보이고,
9090번으로 접속하면 페이지는 보이는데 그림파일이 안보이는 구조였다.

설명이 다소 복잡했는데, 이번에 포트를 9090으로 통일했다.

큰 사이트도 아닌데 굳이 아파치 – 톰캣 분리가 필요 없다고 판단했다.

아파치 웹서버는 기동 중지했고, 톰캣의 server.xml 을 일부 수정했다.

server.xml 최하단에

<Context docBase=”실제 바라볼 폴더경로” path=”/매핑주소” reloadable=”true”/></Host>

를 넣어주면 “도메인/매핑주소”로 접속했을 때 톰캣 내장 웹서버를 사용하게 된다.

예) <Context docBase=”/home/test1/test2/pds” path=”/pds” reloadable=”true”/></Host>

 

[javascript] 자바스크립트 선택정렬 (자바스크립트 배열 정렬)

[javascript] 자바스크립트 선택정렬 (자바스크립트 배열 정렬)

// 파일객체 배열 이름순 정렬하기
function sortFileArray(_fileArr) {
    if (_fileArr == null || _fileArr.length < 2) {
        return _fileArr;
    }
 
    var fileCount = _fileArr.length;
    var tempObj = null;
        for (var i=0; i<fileCount; i++) {
            for (var k=i+1; k<fileCount; k++) {

                // 문자열 배열인 경우

                // if (checkShouldChangeOrder(_fileArr[i], _fileArr[k])) {

               

                // 파일 객체 배열인 경우, 객체.name 순으로 정렬
                if (checkShouldChangeOrder(_fileArr[i].name, _fileArr[k].name)) {
                    tempObj = _fileArr[i];
                    _fileArr[i] = _fileArr[k];
                    _fileArr[k] = tempObj;
                }
            }
        }
 
    return _fileArr;
}

// 문자열 순서 바꿔야 하는지 검사. 순서 바꿔야 하는 경우 true 리턴
function checkShouldChangeOrder(_str1, _str2) {
    if (_str1 == null) {
        _str1 = “”;
    }
 
    if (_str2 == null) {
        _str2 = “”;
    }
 
    var len1 = _str1.length;
    var len2 = _str2.length;

    // 공통길이 계산
    var comLen = len1;
    if (comLen > len2) {
        comLen = len2;
    }
 
    var ch1 = 0;
    var ch2 = 0;
    for (var i=0; i<comLen; i++) {
        ch1 = parseInt(_str1.charCodeAt(i), 10);
        ch2 = parseInt(_str2.charCodeAt(i), 10);
      
        if (ch1 > ch2) {
            return true;
        } else if (ch1 < ch2) {
            return false;  
        }
    }
 

    // 공통길이까지 내용 같을 경우 길이순 정렬
    if (len1 > len2) {
        return true;
    } else if (len1 < len2) {
        return false;
    }
 
    return false;
}

[javascript] 자바스크립트 -1 은 true

[javascript] 자바스크립트 -1 은 true

자바스크립트 1 은 true.

자바스크립트 0 은 false.

여기까지는 그렇다 치자.

자바스크립트 -1 은 true 다.

다음은 테스트 코드다.

function testNum2Bool(_num) { if (_num) { return true; } else { return false; } }

라는 함수 생성.

testNum2Bool(1)
결과 => true

testNum2Bool(0)
결과 => false

testNum2Bool(-1)
결과 => true

이걸 찾아보게 된 이유는 일하다가 if (str.indexOf(“찾는 문자열”)) { … } 이라는 코드를 보고 의구심이 들었기 때문이다.

indexOf는 대상 문자열에서 찾는 문자열이 존재하는 경우 위치를 리턴하는 함수이다.

예를 들어, 대상 문자열에서 찾는 문자열이 발견되지 않으면 -1을 리턴한다.

문제는 자바스크립트가 -1 을 true로 인식한다는 점이다.

찾는 문자열이 첫번째 위치하는 경우(0을 리턴하는 경우)를 제외하고, 문자열이 존재하든 존재하지 않든 if 문 안쪽을 타게 된다는 뜻이다.

정리하면, indexOf는 정석대로 if (str.indexOf(“찾는 문자열”) > -1) { … } 로 코딩해야 한다.

자바스크립트 형 변환은 절대 믿을게 못된다.

[TOMCAT] 톰캣 세션 클러스터링 안되는 문제

[TOMCAT] 톰캣 세션 클러스터링 안되는 문제

아파치 없이 톰캣 2개만으로 세션 클러스터링 묶을 경우, 세션 클러스터링 안되는 문제 발생.

web.xml 의 버전을 2.5에서 3.1로 수정하였더니 잘 된다고 함.

AS-IS

<web-app xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns=”http://java.sun.com/xml/ns/javaee” xmlns:web=”http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd” xsi:schemaLocation=”http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd” version=”2.5″>

TO-BE

<web-app xmlns=”http://xmlns.jcp.org/xml/ns/javaee
         xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance
         xsi:schemaLocation=”http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd” version=”3.1″ metadata-complete=”true”>

로그 비교

스프링 로그상 web.xml 버전이 2.5 일 경우 “org.apache.catalina.ha.session.DeltaManager startInternal” 이 기록되지 않음.

web.xml 버전이 3.1 일 경우 다음 로그가 기록됨.

5월 20, 2019 11:02:36 오전 org.apache.catalina.ha.session.DeltaManager startInternal
정보: Register manager localhost# to cluster element Engine with name Catalina
5월 20, 2019 11:02:36 오전 org.apache.catalina.ha.session.DeltaManager startInternal
정보: Starting clustering manager at localhost#
5월 20, 2019 11:02:36 오전 org.apache.catalina.ha.session.DeltaManager getAllClusterSessions
정보: Manager [localhost#]: skipping state transfer. No members active in cluster group.

로그에 “DeltaManager startInternal” 과 “DeltaManager getAllClusterSessions” 가 기록되지 않을 경우 web.xml 버전을 확인해볼 것.

[Eclipse] 이클립스 workspace 경로 삭제 방법

[Eclipse] 이클립스 workspace 경로 삭제 방법

아래 그림처럼 표시된 Eclipse Launcher 의 workspace 경로 콤보박스의 내역을 삭제하는 방법이다. 이제는 유효하지 않은 경로들이 표시되어서 지우고 싶은 경우가 있다.

이클립스 기동 > 상단 메뉴의 Windows > Preferences > General > Startup & Shudown > Workspaces > 원하는 경로들을 선택하고, [Remove] 버튼을 클릭하면 된다.

 

[VBS] JAVA ProcessBuilder 로 return value 얻는 법 (VBS exitValue)

[VBS] JAVA ProcessBuilder 로 return value 얻는 법 (VBS exitValue)

MainClass.java 

package com;

public class MainClass {

    public static void main(String[] args) {
        try {
            String[] str = new String[2];
            str[0] = WScript”;
            str[1] = C:\\test\\test1.vbs”;

            ProcessBuilder processBuilder = new ProcessBuilder(str);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();
            process.waitFor();

            System.out.println(“exitValue : “ + process.exitValue());
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 

 

test1.vbs

Dim result
result = “1”
WScript.Quit(result)

 

[JAVA] 루씬 (Lucene 8.0.0)

[JAVA] 루씬 (Lucene 8.0.0)

오늘은 저녁/밤 내내 루씬을 공부했다.

0. 루씬(Lucene) 간략정리

– 강력한 색인과 검색 기능 지원

– 약 3MB 정도의 작은 라이브러리

– 창시자 더그 커팅은 하둡의 창시자이기도 함

– 루씬은 오직 색인과 검색에만 집중

— 루씬의 관심사 : 문서 텍스트 분석, 색인에 문서 추가, 색인, 질의 실행, 검색 질의 생성, 결과 출력

— 루씬이 관심 갖지 않는 것 : 검색 대상, 검색 대상 텍스트 확보, 분석 인터페이스, 관리 인터페이스, 검색 화면 인터페이스

– 루씬에서 발전/파생된 프로젝트에는 다음과 같은 것들이 있음

— 엘라스틱 서치, 솔라, 너치, 하둡

– 루씬 3점대 버전 책을 갖고 있는데, 3점대에서는 첫번째 글자에 와일드 카드(? 또는 *)를 넣을 수 없음.

— 이에 따라 가장 최신 버전인 8.0 버전으로 코드를 재작성함. 확인결과 첫번째 글자에도 와일드 카드 사용 가능.

1. 필요한 라이브러리 : lucene-core-8.0.0.jar

* 라이브러리 다운로드 주소 : https://mvnrepository.com/artifact/org.apache.lucene/lucene-core

2. 테스트 코드
특정 폴더(dataDir)에 txt 파일을 여러 개 넣어놓고, IndexManager.java 의 main 메서드를 실행한다.
그러면 색인 파일들이 결과폴더(indexDir)에 쌓인다.
이어서, SearchManager.java 의 main 메서드를 실행하여 파일 내용을 검색해본다.
2-1. IndexManager

package com.thkmon.lucene;

import java.io.File;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

public class IndexManager {

    public static void main(String[] args) {
        IndexManager indexMng = new IndexManager();
        indexMng.doIndex();
    }
    
    
    /**
     * 색인하기
     */

    public void doIndex() {
        
        // 색인 결과파일 폴더 지정
        String indexDir = “C:\\index”;
        
        // 색인할 txt 파일들이 있는 대상 폴더 지정
        String dataDir = “C:\\개인폴더\\개발작업\\개인사이트제작”;
        
        Directory dir = null;
        IndexWriterConfig config = null;
        IndexWriter indexWriter = null;
        
        try {
            dir = FSDirectory.open(new File(indexDir).toPath());
            config = new IndexWriterConfig(new StandardAnalyzer());
            indexWriter = new IndexWriter(dir, config);
            
            File[] fileArr = new File(dataDir).listFiles();
            int fileCount = fileArr.length;
            
            File file = null;
            for (int i=0; i<fileCount; i++) {
                file = fileArr[i];
                
                if (file.isDirectory()) {
                    continue;
                }
                
                if (file.isHidden()) {
                    continue;
                }
                
                if (!file.exists()) {
                    continue;
                }
                
                if (!file.canRead()) {
                    continue;
                }
                
                if (file.getName().toLowerCase().endsWith(“.txt”)) {
                    indexFile(indexWriter, file);
                }
            }
            
            System.out.println(indexWriter.numRamDocs());
            
        } catch (Exception e) {
            e.printStackTrace();
            
        } finally {
            try {
                if (indexWriter != null) {
                    indexWriter.close();
                }
            } catch (Exception e) {
                indexWriter = null;
            }
            
            try {
                if (dir != null) {
                    dir.close();
                }
            } catch (Exception e) {
                dir = null;
            }
        }
    }

    
    public void indexFile(IndexWriter indexWriter, File file) throws Exception {
        Document doc = getDocument(file);
        indexWriter.addDocument(doc);
    }
    
    
    private Document getDocument(File file) throws Exception {
        String fileContent = FileReadUtil.readFileToString(file, “MS949”, ” “);
        
        Document doc = new Document();
        
        doc.add(new TextField(“contents”, String.valueOf(fileContent), Field.Store.YES));
        doc.add(new TextField(“filename”, String.valueOf(file.getName()), Field.Store.YES));
        doc.add(new TextField(“fullpath”, String.valueOf(file.getCanonicalPath()), Field.Store.YES));
        
        System.out.println(“canonicalPath : “ + file.getCanonicalPath());
        System.out.println(“filename : “ + file.getName());
        System.out.println(“fileContent : “ + fileContent);
        System.out.println(“====================”);
        
        return doc;
    }
}

 

2-2. SearchManager.java

package com.thkmon.lucene;

import java.io.File;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

public class SearchManager {

    public static void main(String[] args) {
        SearchManager searchMng = new SearchManager();
        searchMng.doSearch();
    }
    
    
    /**
     * 검색하기
     */

    public void doSearch() {
        
        // 색인 결과파일 폴더 지정
        String indexDir = “C:\\index”;
        
        // 검색 키워드
        String keyword = “*깨끗*만들자*”;
        
        Directory dir = null;
        IndexReader indexReader = null;
        IndexSearcher indexSearcher = null;
        
        try {
            dir = FSDirectory.open(new File(indexDir).toPath());
            indexReader = DirectoryReader.open(dir);
            indexSearcher = new IndexSearcher(indexReader);
            
            // 정확한 어절을 검색하려면 TermQuery 객체 사용.
            // Query query = new TermQuery(new Term(“filename”, keyword));
            
            // 와일드 카드(? 또는 *) 사용하여 검색하려면 WildcardQuery 객체 사용.
            Query query = new WildcardQuery(new Term(“filename”, keyword));
            
            TopDocs docs = indexSearcher.search(query, 10);
            
            int docCount = 0;
            if (docs.scoreDocs != null) {
                docCount = docs.scoreDocs.length;
                System.out.println(docCount);
                Document doc = null;
                for (int i=0; i<docCount; i++) {
                    doc = indexSearcher.doc(docs.scoreDocs[i].doc);
                    System.out.println(doc.get(“contents”));
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            
        } finally {
            try {
                if (indexReader != null) {
                    indexReader.close();
                }
            } catch (Exception e) {
                indexReader = null;
            }
            
            try {
                if (dir != null) {
                    dir.close();
                }
            } catch (Exception e) {
                dir = null;
            }
        }
    }
    
}

2-3. FileReadUtil.java

package com.thkmon.lucene;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class FileReadUtil {
    
    /**
     * 파일 읽어서 ArrayList 로 리턴
     *
     * @param file
     * @param encode
     * @return
     * @throws IOException
     * @throws Exception
     */

    public static ArrayList<String> readFile(File file, String encode) throws IOException, Exception {
        if (file == null || !file.exists()) {
            return null;
        }

        ArrayList<String> resultList = null;

        FileInputStream fileInputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;

        try {
            fileInputStream = new FileInputStream(file);
            inputStreamReader = new InputStreamReader(fileInputStream, encode);
            bufferedReader = new BufferedReader(inputStreamReader);

            String oneLine = null;
            while ((oneLine = bufferedReader.readLine()) != null) {
                if (resultList == null) {
                    resultList = new ArrayList<String>();
                }

                resultList.add(oneLine);
            }

        } catch (IOException e) {
            throw e;

        } catch (Exception e) {
            throw e;

        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (Exception e) {
                bufferedReader = null;
            }

            try {
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
            } catch (Exception e) {
                inputStreamReader = null;
            }

            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (Exception e) {
                fileInputStream = null;
            }
        }

        return resultList;
    }
    
    
    /**
     * 파일 읽어서 String 으로 리턴
     *
     * @param file
     * @param encode
     * @param delimiter
     * @return
     * @throws IOException
     * @throws Exception
     */

    public static String readFileToString(File file, String encode, String delimiter) throws IOException, Exception {
        StringBuffer buff = new StringBuffer();
        
        ArrayList<String> strList = readFile(file, encode);
        if (strList != null && strList.size() > 0) {
            int listCount = strList.size();
            for (int i=0; i<listCount; i++) {
                buff.append(strList.get(i));
                buff.append(delimiter);
            }
        }
        
        return buff.toString();
    }
}

TSTCON32.EXE : ActiveX Control Test Container(액티브엑스 컨트롤 테스트 컨테이너)

TSTCON32.EXE : ActiveX Control Test Container(액티브엑스 컨트롤 테스트 컨테이너)

 

cf) TSTCON32.EXE 사용방법 : https://blog.naver.com/bb_/221640207756

Windows 라이선스가 곧 만료됩니다

Windows 라이선스가 곧 만료됩니다

Windows 라이선스가 곧 만료됩니다

설정에서 Windows 정품 인증을 받아야 합니다.

라는 메시지가 계속 뜰 경우

1. 윈도우 메뉴 – cmd 검색 (명령 프롬프트) – cmd 위에서 마우스 우클릭 – [관리자 권한으로 실행]

2. slmgr /dlv 입력 후 엔터

=> 윈도우 만료일을 볼 수 있음.

3. slmgr /rearm 입력 후 엔터

=> 윈도우 라이센스 기간 연장하는 명령어

윈도우 재시작 후 slmgr /dlv 입력하여 기간 만료일이 표시되지 않으면 성공.

DB 형상관리 오픈소스 도구 (Flyway)

DB 형상관리 오픈소스 도구 (Flyway)

https://flywaydb.org/

참고링크 (DB도 형상관리를 해보자 / 신용우 님)

context-datasource.xml 위치, context-datasource.xml 암호화

context-datasource.xml 위치, context-datasource.xml 암호화

마이바티스(Mybatis)를 사용할 때 context-datasource.xml 안에 데이터베이스(DB) URL, 포트, 아이디, 비밀번호를 적어넣게 된다.

아래와 같은 형태다.

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans
 xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance
 xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd“>
    <bean id=”dataSource” class=”org.apache.commons.dbcp.BasicDataSource” destroy-method=”close”>
         <property name=”driverClassName” value=”com.mysql.jdbc.Driver” />
         <property name=”url” value=”jdbc:mysql://아이피주소:포트/디비명” />
         <property name=”username” value=”아이디” />
         <property name=”password” value=”비밀번호” />
    </bean>
</beans>

이 경우 치명적인 단점이 있는데 GitHub 등에 커밋할 경우 아이디나 비밀번호가 유출될 수 있다는 점이다.

물론 해당 파일을 싱크로나이즈 대상에서 제외시킬 수도 있겠지만 실수할 가능성이 존재한다.

따라서 context-datasource.xml 안에 암호화된 값을 기입해놓거나, 일단 의미없는 값을 넣어놓고 변경하는 방법을 소개한다.

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans
 xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance
 xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd“>
 <!–bean id=”dataSource” class=”org.apache.commons.dbcp.BasicDataSource” destroy-method=”close”–>
 <bean id=”dataSource” class=”com.thkmon.webstd.db.NewDataSource” destroy-method=”close”>
 <property name=”driverClassName” value=”com.mysql.jdbc.Driver”/>
 <property name=”url” value=”1″/>
 <property name=”username” value=”1″/>
 <property name=”password” value=”1″/>
 </bean>
</beans>

context-datasource.xml 파일에서 BasicDataSource 클래스명이 들어있는 라인(<bean id=”dataSource” class=”org.apache.commons.dbcp.BasicDataSource” destroy-method=”close”>)을 주석처리하고, 그 아래에

<bean id=”dataSource” class=”com.thkmon.webstd.db.NewDataSource” destroy-method=”close”> 라는 라인을 추가한다.

경로는 달라져도 좋으나, 해당 경로의 패키지와 클래스를 새로 생성해야 한다.

여기서 com.thkmon.webstd.db 패키지는 필자가 직접 만든 패키지이고, NewDataSource 클래스도 새로 만든 클래스이다.

context-datasource.xml 파일의 url, username, password 에는 암호화된 값 또는 의미없는 값을 넣어둔다.

이어서 com.thkmon.webstd.db 패키지를 만들고, 해당 패키지 안에 NewDataSource.class 파일을 추가한다.

내용은 아래와 같이 작성한다. (BasicDataSource 를 상속)

package com.thkmon.webstd.db;

import java.sql.SQLException;

import org.apache.commons.dbcp.BasicDataSource;

public class NewDataSource extends BasicDataSource {

    public synchronized void close() throws SQLException {
        super.close();
    }
 
    public synchronized void setDriverClassName(String driverClassName) {
        super.setDriverClassName(driverClassName);
    }

    public synchronized void setUrl(String url) {
        String newUrl = “jdbc:mysql://아이피주소:포트/디비명”;
        super.setUrl(newUrl);
    }

    public void setUsername(String username) {
        String newUsername = “newUserName”;
        super.setUsername(newUsername);
    }

    public void setPassword(String password) {
        String newPassword = “newPassword”;
        super.setPassword(newPassword);
    }
}

BasicDataSource 클래스를 상속하였으므로 setUrl, setUsername, setPassword 등이 오버라이딩 되어 있다.

위 예제에서는 문자열 값이 하드코딩되어 있는데 그래서는 의미가 없고, 메서드 내용을 적절히 변경하면 된다.

첫번째 방법으로는, context-datasource.xml 쪽에는 의미없는 값을 넣어두고, setUrl, setUsername, setPassword 메서드 안에서는 특정 위치의 설정 파일(properties, config, xml 등)을 읽어들여서 그 안의 값으로 변경한다.

다른 방법으로는, context-datasource.xml 쪽에 암호화된 값을 넣어두고, setUrl, setUsername, setPassword 메서드 안에서 복호화를 수행하여 처리하면 된다. 암복호화 로직은 여기서 따로 설명하지 않는다.

자세한 것은 [JAVA] AES128 암호화 예제(https://blog.naver.com/bb_/221332286531)를 참고할 것.

[자작VBA함수] indexOf, lastIndexOf

[자작VBA함수] indexOf, lastIndexOf

‘========================================

Function indexOf(cell, textToFind)

    ‘cell의 값을 가져옴
    Dim val
    val = Range(cell, cell).Value

    ‘cell의 값 1번째 글자부터 textToFind 값 찾기
    Dim idx
    idx = InStr(1, val, textToFind, vbTextCompare)

    ‘인덱스 값을 리턴
    indexOf = idx

End Function

‘========================================

Function lastIndexOf(cell, textToFind)
    ‘cell의 값을 가져옴

    Dim val

    val = Range(cell, cell).Value
    ‘cell의 값 뒤에서부터(-1) textToFind 값 찾기

    Dim idx

    idx = InStrRev(val, textToFind, -1, vbTextCompare)
    ‘인덱스 값을 리턴

    lastIndexOf = idx
End Function

‘========================================

참고) inStr 함수

앞에서부터 문자열을 찾는 함수. 사용방법은 다음과 같다.

inStr(찾기시작위치, 대상텍스트, 찾을문자열, 텍스트비교방식)

참고 2) inStrRev 함수

뒤에서부터 문자열을 찾는 함수. 사용방법은 다음과 같다.

inStrRev(대상텍스트, 찾을문자열, 찾기시작위치, 텍스트비교방식)

 

[JAVA] BitBlt 예제 (자바 BitBlt 예제)

[JAVA] BitBlt 예제 (자바 BitBlt 예제)

자바에서 BitBlt 함수 사용하는 방법이다.

아무리 생각해도 나중에 까먹을 것 같아 잠을 아껴 여기 써둔다.

BitBlt 는 윈도우 OS 에서 제공하는 함수로, 화면의 고속복사를 위한 함수다.

단순히 빠른 속도로 화면에 그래픽을 찍어낼 수 있다는 이유 하나로, 어렸을 적 VB로 게임 만들 때 종종 사용하곤 했다.

필요한 라이브러리는 2개다.

(1) jna-4.5.0.jar

(2) jna-platform-4.5.0.jar

참고로 JNA란 Java Native Access의 약자로 자바에서 윈도우 네이티브 함수(ex : Win32 API)에 쉽게 접근하도록 돕는 라이브러리이다.

자바 자체가 크로스 플랫폼 언어인지라 이러한 라이브러리의 도움을 받지 않으면 윈도우 함수에 접근할 수가 없다.

BitBlt 함수를 자세히 설명하면, 윈도우의 DC(Device Context) 영역끼리 고속 복사를 수행하는 함수다.

참고로 DC란 Device Context의 약자이고, 윈도우에서 정의한 구조체로, 화면 출력에 대한 구조체라고 한다.

굳이 자바에서 BitBlt 사용하는 법을 찾아낸 것은, 자바로 1초에 수십번(60 FPS ~ 100 FPS) 여러 개의 이미지를 찍어내면 느리기 때문이다.

그 이유는 네이티브 자바가 느리기 때문이 아니라, 정확히는 자바 JFrame 에 BufferdImage 를 그리는 로직이 게임 프로그래밍에 알맞지 않기 때문이다.

자바의 JFrame 은 자체적으로 더블 버퍼링이 적용되어 있으므로 특히 그림 여러 개를 반복 출력할 경우 느리고, 운영체제 관계없이 동작하기 때문에 직접 윈도우 API를 사용하는 것보다 상대적으로 느리다.

1. 예제 코드 작성

우선 아래 코드를 작성한다. MainClass.java 와 WinGDIConf.java 를 작성한다.

1-1. MainClass.java

package com.thkmon.gamelib.main;

import java.util.HashMap;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.GDI32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.RECT;
import com.sun.jna.platform.win32.WinUser.WNDENUMPROC;
import com.thkmon.gamelib.prototype.WinGDIConf;

public class MainClass {

    public static void main(String[] args) {

        try {
            HashMap<String, HWND> map = getHWNDMap();
            
            // 그림판 핸들 윈도우(HWND) 가져오기 (미리 그림판을 열어둬야 함)
            HWND paintHWND = map.get(“MSPaintApp”);
            if (paintHWND == null) {
                throw new Exception(“그림판을 켠 후 다시 시도해주세요.”);
            }
            
            // 메모장 핸들 윈도우(HWND) 가져오기 (미리 메모장을 열어둬야 함)
            HWND notepadHWND = map.get(“Notepad”);
            if (notepadHWND == null) {
                throw new Exception(“메모장을 켠 후 다시 시도해주세요.”);
            }
            
            HDC paintHDC = User32.INSTANCE.GetDC(paintHWND);
            HDC notepadHDC = User32.INSTANCE.GetDC(notepadHWND);
            
            // 그림판 300, 300 위치에 메모장의 0~200, 0~100 영역을 고속복사
            GDI32.INSTANCE.BitBlt(paintHDC, 300, 300, 200, 100, notepadHDC, 0, 0, WinGDIConf.SRCCOPY);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    
    /**
     * 윈도우 클래스 네임 문자열(String)을 put 하면 윈도우 핸들 윈도우(HWND)를 리턴하는 HashMap.
     * HashMap에는 첫번째 발견된 핸들 윈도우(HWND)만 저장하며, 클래스 네임이 중복될 경우 무시한다.
     * @return
     */

    public static HashMap<String, HWND> getHWNDMap() {

        final HashMap<String, HWND> hwndMap = new HashMap<String, HWND>();

        User32.INSTANCE.EnumWindows(new WNDENUMPROC() {
            int count = 0;

            public boolean callback(HWND hWnd, Pointer arg1) {
                char[] windowText = new char[512];
                User32.INSTANCE.GetWindowText(hWnd, windowText, 512);
                String wText = Native.toString(windowText);
                RECT rectangle = new RECT();
                User32.INSTANCE.GetWindowRect(hWnd, rectangle);
                
                if (wText.isEmpty() || !(User32.INSTANCE.IsWindowVisible(hWnd) && rectangle.left > -32000)) {
                    return true;
                }

                String clsName = getClassNameFromHandle(hWnd);
                if (clsName != null && clsName.length() > 0) {

                    StringBuffer buff = new StringBuffer();

                    buff.append(“번호:” + (++count));
                    buff.append(“,텍스트:” + wText);
                    buff.append(“,” + “위치:(“);
                    buff.append(rectangle.left + “,” + rectangle.top + “)~(“);
                    buff.append(rectangle.right + “,” + rectangle.bottom);
                    buff.append(“),” + “클래스네임:” + clsName);

                    System.out.println(buff.toString());

                    // HashMap에는 첫번째 발견된 핸들 윈도우(HWND)만 저장하며, 클래스 네임이 중복될 경우 무시한다.
                    if (hwndMap.get(clsName) == null) {
                        hwndMap.put(clsName, hWnd);
                    }
                }

                return true;
            }
        }, null);

        return hwndMap;
    }
    
    
    /**
     * 핸들 윈도우(HWND)의 클래스 네임을 얻는다.
     *
     * @param hWnd
     * @return
     */

    public static String getClassNameFromHandle(HWND hWnd) {
        if (hWnd == null) {
            return “”;
        }

        // 핸들의 클래스 네임 얻기
        char[] c = new char[512];
        User32.INSTANCE.GetClassName(hWnd, c, 512);
        String clsName = String.valueOf(c).trim();
        return clsName;
    }
}

1-2. WinGDIConf.java

BitBlt 관련 상수들을 모아둔 인터페이스. MainClass에서 해당 인터페이스의 상수를 BitBlt 아규먼트에 활용할 수 있다.

package com.thkmon.gamelib.prototype;

public interface WinGDIConf {
    
    // SRCCOPY : 복사한다.
    public Integer SRCCOPY = new Integer(0x00CC0020);
    public Integer SRCPAINT = new Integer(0x00ee0086);
    public Integer SRCAND = new Integer(0x008800c6);
    public Integer SRCINVERT = new Integer(0x00660046);
    public Integer SRCERASE = new Integer(0x00440328);

    public Integer NOTSRCCOPY = new Integer(0x00330008);
    public Integer NOTSRCERASE = new Integer(0x001100a6);
    // MERGECOPY : 비트맵을 AND 연산한다.
    public Integer MERGECOPY = new Integer(0x00c000ca);
    // MERGEPAINT : 비트맵을 OR 연산한다.
    public Integer MERGEPAINT = new Integer(0x00bb0226);

    public Integer PATCOPY = new Integer(0x00f00021);
    public Integer PATPAINT = new Integer(0x00fb0a09);
    public Integer PATINVERT = new Integer(0x005a0049);
    
    // DSTINVERT : 화면을 반전시킨다.
    public Integer DSTINVERT = new Integer(0x00550009);
    // WHITENESS : 대상영역을 흰색으로 채운다.
    public Integer WHITENESS = new Integer(0x00ff0062);
    // BLACKNESS : 대상영역을 검정색으로 채운다.
    public Integer BLACKNESS = new Integer(0x00000042);
    public Integer CAPTUREBLT = new Integer(0x00CC0020 | 0x40000000);
    public Integer Black = new Integer(0x00000000);

    public long WS_CHILD = 0x40000000L;
    public long WS_VISIBLE = 0x10000000L;
    public long MS_SHOWMAGNIFIEDCURSOR = 0x0001L;

    public long WS_EX_TOPMOST = 0x00000008L;
    public long WS_EX_LAYERED = 0x00080000;
    public long WS_EX_TRANSPARENT = 0x00000020L;

    public long WS_CLIPCHILDREN = 0x02000000L;

    public long MW_FILTERMODE_EXCLUDE = 0;
}

참고로, 프로그램 실행시 getHWNDMap 메서드에서, 핸들 윈도우(HWND) 정보를 콘솔에 출력하는 코드가 있다.

그림판이 열려 있다면 MSPaintApp 라는 문자열이, 메모장이 열려 있으면 Notepad 라는 문자열이 콘솔에 찍힐 것이다.

 

2. 예제 실행

예제 프로그램을 실행한다. 단, 그림판 1개와 메모장 1개를 열어서 화면에 가리지 않도록 위치시킨 후 실행한다.

3. 예제 실행 결과

그림판 300, 300 위치에 메모장의 0~200, 0~100 영역을 성공적으로 고속복사했다.

참고로 BitBlt 로 그려진 이미지는 해당 핸들 윈도우(HWND)를 움직이거나 최소화할 경우 곧바로 사라진다. 그만큼 가볍고 빠르게 화면을 복사하기에 유용한 함수이다.


아래 사이트를 참고하였다.

참고사이트) http://soen.kr/lecture/win32api/lec6/lec6-4-3.htm

[TOMCAT] _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit

[TOMCAT] _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit

 

서버에 jsp 패치 이후 특정 페이지에서 아래와 같이 500 에러가 발생하였다.

우선, 해당 페이지 소스 코드에 이상이 있는지 확인해봤지만 이상 없었다.

 

HTTP Status 500

type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception
org.apache.jasper.JasperException: Unable to compile class for JSP:

An error occurred at line: 56 in the generated java file
The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit

Stacktrace:
 org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.java:93)
 org.apache.jasper.compiler.ErrorDispatcher.javacError(ErrorDispatcher.java:330)
 org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:451)
 org.apache.jasper.compiler.Compiler.compile(Compiler.java:319)
 org.apache.jasper.compiler.Compiler.compile(Compiler.java:298)
 org.apache.jasper.compiler.Compiler.compile(Compiler.java:286)
 org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:565)
 org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:309)
 org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:308)
 org.apache.jasper.servlet.JspServlet.service(JspServlet.java:259)
 javax.servlet.http.HttpServlet.service(HttpServlet.java:688)

note The full stack trace of the root cause is available in the Apache Tomcat/5.5.31 logs.

Apache Tomcat/5.5.31

 

 

검색 결과 tomcat 의 conf 폴더 내의 web.xml 을 수정해주면 해결 가능했다.

web.xml 의 <servlet> 태그 안에 아래 코드를 넣고 톰캣을 재기동하면 된다.

참고사이트) https://bulkywebdeveloper.tistory.com/6

 

<servlet>

(중략)

<init-param>
    <param-name>mappedfile</param-name>
    <param-value>false</param-value>
</init-param>

(중략)

</servlet>

 

 

 

원인은 jsp 파일 내용이 길어지면서 메서드 길이 제한인 65535 를 초과했기 때문이라고 한다.

아래의 오라클에서 제공하는 페이지를 참고할 것.

참고사이트) https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.11

4. 11. Limitations of the Java Virtual Machine

(중략)

The per-class or per-interface constant pool is limited to 65535 entries by the 16-bit constant_pool_count field of the ClassFile structure (§4.1).

(중략)

The number of fields that may be declared by a class or interface is limited to 65535 by the size of the fields_count item of the ClassFile structure (§4.1).

(중략)

The number of methods that may be declared by a class or interface is limited to 65535 by the size of the methods_count item of the ClassFile structure (§4.1).

(중략)

  

한 편 톰캣에 설정한 mappedfile의 의미는, mappedfile 이 true 일 경우 톰캣 컨테이너가 HTML 코드(jsp 를 컴파일한 결과 코드)를 라인 수 만큼의 out.write 로 출력한다고 한다.

반대로 mappedfile 이 false 일 때는 HTML 코드를 out.write 한 번에 출력한다고 한다.

(When mappedfile is true, container generates “out.print()” for each HTML text line in the JSP file. And when false, the HTML text from multiple lines are concatenated and output in one “out.print()” and that’s how it ease debugging.)

과거 버전인 톰캣 3과 4에서는 mappedfile 디폴트값이 false 인데, 톰캣 5는 mappedfile 디폴트 값이 true 라고 한다.

(Older version of tomcat (3,4) have this option by default false and newer version starting from tomcat 5 have this option default true.)

[TOMCAT] FCM에서 받은 푸시 알림 한글 깨지는 현상 (Dfile.encoding 을 추가하여 해결)

[TOMCAT] FCM에서 받은 푸시 알림 한글 깨지는 현상 (Dfile.encoding 을 추가하여 해결)

* 모바일 쪽은 잘 모르지만, 이것 때문에 회사에서 모바일 팀과 몇 시간 고생했기 때문에 기록해둔다.

FCM에서 받은 한글 푸시 알림이 모바일(안드로이드/아이폰) 에서 깨져보이는 현상이 있었다.

푸시 중계 서버가 FCM(Firebase Cloud Messaging)에 모바일 알림 관련 정보를 json 으로 쏘면,

FCM이 안드로이드/아이폰 기기로 알림을 보내주는 식으로 처리된 상태였다.

참고로 푸시 중계 서버는 회사 모바일팀이 자체 개발한 것으로, 톰캣으로 기동하였다.

문제는 개발용 푸시 중계 서버를 이용했을 때는 인코딩 문제가 없었는데,

운영 환경에서 푸시 중계 서버를 이용했을 때 한글 알림이 깨지는 문제가 발생했다.

운영은 윈도우 서버이고, 따라서 푸시 중계 서버는 톰캣을 (sh 이 아닌) bat 파일로 띄워주도록 구성되어 있었다.

json 에 포함되는 한글 문자열 부분을 URLEncoder.encode(str, “UTF-8”) 메서드로

아무리 감싸보아도 소용이 없었고 (1번이 안되어서 2번도 감싸보았다),

혹시나 해서 EUC-KR 로 감싸도 소용이 없었다.

UTF-8 인코딩을 하지 않으면 깨진 문자열이 모바일 화면에 뜨는데,

UTF-8 인코딩을 하면 인코딩된 상태의 문자열이 화면에 뜨는 문제였다.

모바일팀은 안드로이드/아이폰 각 기기에서 디코드를 처리하도록 소스코드를 고쳐야 하나 고민하기 시작했다.

결론은 톰캣 카탈리나 옵션에 Dfile.encoding 을 추가하여 해결했다.

얼마 전에도 Dfile.encoding 을 추가해서 해결한 문제가 있었는데, 이것으로 해결되리라곤 상상도 하지 못했다.

톰캣 폴더 안쪽 bin 폴더의 catalina.bat 에 아래와 같이 옵션을 추가해주었다.

(문자열 “set CLASSPATH=” 를 찾아서 그 라인 바로 아래에 넣어주면 된다.)

set CATALINA_OPTS=-Xms1024m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m -Dfile.encoding=”UTF-8″

이번은 윈도우 서버여서 catalina.bat 을 수정하였는데, 리눅스라면 톰캣폴더/bin/catalina.sh 파일을 수정하면 된다.

* 관련 게시글 : 톰캣 펌사이즈 설정 (tomcat permsize) – java.lang.OutOfMemoryError (http://blog.naver.com/bb_/221066553152)
* 관련 게시글 : [TOMCAT] 파일 내부의 한글 깨지는 문제 (Dfile.encoding 추가하여 해결) (http://blog.naver.com/bb_/221477811469)

자이썬(Jython) Hello World

자이썬(Jython) Hello World

학원 다닐 때 자이썬(Jython) 이라는 언어가 있다는 얘기를 듣고,

<그게 뭐야!> 라며 특이하다고만 생각했었는데 생각할수록 괴상하다.

간단하게 말하면 문법은 파이썬인데, 실행은 JVM(자바가상환경) 위에서 돌아간다.

파이썬을 만든 귀도 반 로섬(Guido van Rossum)도 신기하지만,

자이썬을 만든 짐 후구닌(Jim Hugunin) 도 정말 신기하다. 아니 신기함을 넘어 쇼킹하다.

(만들까 생각은 해볼 수 있지만 진짜 만드는 건… 정말 차원이 다른 문제다.)

자이썬은 다음과 같은 경우에 사용하는 것 같다.

=> 파이썬으로 짠 프로그램에서, 자바로 만들어져 있는 라이브러리를 사용해야 할 때.

실전에서 사용해본 적이 없어서 그런가 이유가 마음에 와닿진 않는다.

개인적으로는 다음과 같은 이유에서 사용하려고 한다.

=> 파이썬 프로그램을 짜서 배포하고 싶은데, 사용자들 입장에서 JVM 환경이 구성하기 쉽고 접근성이 높다고 판단했음.

아무튼 자이썬으로 헬로 월드를 짜보자.

(기본적으로 JDK(자바개발도구) 는 깔려있어야 하겠다.)

1. 자이썬(Jython) 다운로드

http://jython.org 에 접속해서 좌측 메뉴의 Download 를 클릭한다.

2019년 3월 31일 기준 2.7 버전까지 나와있는데 파이썬 버전과 동일하다고 보면 된다고 한다.

Installer 또는 Standalone Jar 를 다운받으면 되는데,

필자는 Download Jython 2.7.0 – Standalone Jar 를 클릭해서 jar 파일을 다운받았다.

(파일명 : jython-standalone-2.7.0.jar)

2. 폴더 생성과 jar 파일 이동

C:\jython 폴더를 만들고 jython-standalone-2.7.0.jar 파일을 넣었다.

이어서 hello_world.py 라는 파일을 생성했다.

3. hello_world.py 파일 작성

같은 위치에 hello_world.py 파일을 작성한다. 내용은 단 한 줄…

print(“Hello World”)

를 입력 후 저장한다.

참고로 파이썬 문법은 세미콜론이 필요없다.

4. cmd 에서 자이썬 jar 를 활용해 hello_world 파일 실행

cmd 로 들어가서 자이썬을 실행해보자.

우선 java -version 을 입력했을 때 버전이 제대로 출력되어야 한다.

(여기서 오류가 발생한다면 반드시 JDK(자바개발도구) 를 깔고 다음 과정을 진행하자.)

cd C:\jython

java -jar jython-standalone-2.7.0.jar hello_world.py

위와 같이 명령어를 입력했을 때, 콘솔에 Hello World 가 출력되면 성공이다.

[TOMCAT] 대용량 파일 업로드 실패 (톰캣 post 용량 / tomcat maxPostSize)

[TOMCAT] 대용량 파일 업로드 실패 (톰캣 post 용량 / tomcat maxPostSize)

톰캣에서 대용량 파일 업로드가 계속 실패하는 현상이 있었다. (cf. HTML 문서 서식파일 업로드)

POST로 쏠 때 대용량이면 업로드 되지 않을 수 있다고 한다.

해결방법은 server.xml 파일 Connector 태그 안에 maxPostSize 어트리뷰트를 추가하면 된다.

ex) <Connector port=”8080″ protocol=”HTTP/1.1″ connectionTimeout=”20000″ redirectPort=”8443″ />

해당 코드를,

<Connector maxPostSize=”-1″ port=”8080″ protocol=”HTTP/1.1″ connectionTimeout=”20000″ redirectPort=”8443″ />

위 코드와 같이 변경.

Tomcat 7.0.63 미만에서는 maxPostSize 를 0 으로 설정하면 무제한이고,

Tomcat 7.0.63 이상 및 Tomcat 8.x 이상은 maxPostSize 를 -1 로 설정하면 무제한이라고 한다.

참고로 maxPostSize 는 바이트 기준으로 입력한다.

예를 들어 4 라고 입력하면 4 바이트까지이므로, “abcd”는 업로드 되지만, “abcde”는 5 바이트이므로 업로드 불가능하다.

참고사이트) https://sarc.io/index.php/tomcat/948-tomcat-post-parameter

[JAVASCRIPT] 자바스크립트 select 박스(콤보박스) 옵션 추가하는 방법

[JAVASCRIPT] 자바스크립트 select 박스(콤보박스) 옵션 추가하는 방법

select 박스, 콤보박스라고도 부른다.​

jquery로 제어하면 편하지만, 여기서는 순수 javascript로 제어하는 함수를 적어둔다.

이곳에, 자바스크립트 select 박스(콤보박스) 제어하는 코드를 모아둔다.

1. javascript select 박스(콤보박스) 에서 현재 선택된 값 가져오기


var currentValue = document.getElementById(_comboId).value;

2. javascript select 박스(콤보박스) 에서 현재 선택되어 있는 인덱스 가져오기


// getComboSelectedId : select 태그의 아이디를 파라미터로 넘기면, 현재 선택되어 있는 인덱스를 반환한다.

function getComboSelectedId(_comboId) {
    var comboObj = document.getElementById(_comboId);
    if (comboObj == null) {
        return null;
    }
            
    if (comboObj.options == null || comboObj.options.length == 0) {
        return null;
    }
                    
    var optionCount = comboObj.options.length;
    for (var i=0; i<optionCount; i++) {
        if (comboObj.options[i].selected) {
            return comboObj.options[i].id;
        }
    }
        
    return null;
}

3. javascript select 박스(콤보박스) 특정 옵션값 선택하기


// setComboToSelectOption : select 태그의 아이디와 option 태그의 아이디를 파라미터로 넘기면, 해당 option을 선택한다.
function setComboToSelectOption(_comboId, _itemId) {
    var comboObj = document.getElementById(_comboId);
    if (comboObj == null) {
        return false;
    }
            
    if (comboObj.options == null || comboObj.options.length == 0) {
        return false;
    }
    
    if (_itemId == null) {
        _itemId = “”;
    }
                    
    var optionCount = comboObj.options.length;
    for (var i=0; i<optionCount; i++) {
        if (comboObj.options[i].id == _itemId) {
            comboObj.options[i].selected = true;
            return true;
        }
    }
        
    return false;
}

4. javascript select 박스(콤보박스) 에서 모든 옵션 삭제하기


function deleteComboOptions(_comboId) {
    try {
        var ops = document.getElementById(_comboId).options;
        if (ops != null && ops.length > 0) {
            var lastIdx = ops.length – 1;
            for (var k=lastIdx; k>=0; k–) {
                ops[k] = null;
            }
        }
    } catch (e) {
        alert(“deleteComboOptions ERROR : ” + e);
    }
}

5. javascript select 박스(콤보박스) 에 옵션 추가하기


var ops = document.getElementById(_comboId).options;

var op = new Option();

op.value = “tempValue”;

op.text = “tempText”;

// 옵션을 기본 선택하려면 아래 코드 적용할 것

// op.selected = true;

ops.add(op);

 

[ORACLE] 오라클 함수 Exception 처리 방법 (오라클 try catch exception)

[ORACLE] 오라클 함수 Exception 처리 방법 (오라클 try catch exception)

오라클 함수(Oracle Function)도 자바의 try ~ catch 구문과 같은 처리가 가능할까?

오라클 함수를 실행했을 때 SELECT 한 결과 ROW 가 존재하지 않을 경우,
EXCEPTION 이 발생하고 오라클 함수는 null 을 리턴한다.

예를 들어 다음 함수를 보자.

CREATE OR REPLACE FUNCTION get_something (
    input1  IN  VARCHAR2,
    input2  IN  VARCHAR2
)
RETURN VARCHAR2
IS
    retVal  VARCHAR2 (20);
    temp1  VARCHAR2 (20);
BEGIN
    retVal := ”;
  
    SELECT ‘1’
    INTO temp1
    FROM dual
    WHERE ROWNUM < 1;
 
    retVal := temp1;
   
    IF retVal IS NULL OR retVal = ” THEN
        retVal := ‘default’;
    END IF;
   
    RETURN retVal;
END;

위 함수의 의도는 (마지막 부분을 보면 알겠지만)

결과값이 없을 경우(retVal IS NULL OR retVal = ”) 기본값을 리턴하려고 한다.

하지만, 그 의도대로 동작하지 않으며 함수는 null을 리턴한다.
결과 데이터가 없어서(NO_DATA_FOUND) EXCEPTION 이 발생했기 때문이다.

실제로 SELECT get_something(”, ”) FROM DUAL; 을 실행해보면 null이 나온다.

이러한 NO_DATA_FOUND 익셉션을 회피하기 위해서는, 다음과 같이 BEGIN ~ EXCEPTION ~ END; 구문을 사용하면 된다.

CREATE OR REPLACE FUNCTION get_something (
    input1  IN  VARCHAR2,
    input2  IN  VARCHAR2
)
RETURN VARCHAR2
IS
    retVal  VARCHAR2 (20);
    temp1  VARCHAR2 (20);
BEGIN
    retVal := ”;
  
    BEGIN
        SELECT ‘1’
        INTO temp1
        FROM dual
        WHERE ROWNUM < 1;
       
    EXCEPTION
    WHEN NO_DATA_FOUND THEN
        temp1 := null;
    END;
 
    retVal := temp1;
    IF retVal IS NULL OR retVal = ” THEN
        retVal := ‘default’;
    END IF;
   
    RETURN retVal;
END;

이번에는 SELECT get_something(”, ”) FROM DUAL; 을 실행해보면 리턴값으로 문자열 ‘default’ 가 나온다.

실제 업무에서는 디폴트 값을 돌려주기 보다,

BEGIN ~ EXCEPTION ~ END; 구문을 여러 번 반복해서 원하는 값을 찾을 때까지 SELECT하는 코드를 많이 사용한다.

참고로, NO_DATA_FOUND 뿐 아니라 모든 EXCEPTION 을 커버하기 위해서는, 아래와 같이 WHEN OTHERS THEN 을 사용하면 된다.

    BEGIN
        — 내용
        
    EXCEPTION
    WHEN OTHERS THEN
        — 내용
    END; 

[ORACLE] 오라클 프로시저 작성 (매개변수 OUT 이 존재하는 프로시저 SELECT 방법)

[ORACLE] 오라클 프로시저 작성 (매개변수 OUT 이 존재하는 프로시저 SELECT 방법)

매개변수 OUT 이 존재하는 프로시저를 작성하고, 호출하는 방법에 대해 정리한다.

1. 프로시저 작성 (매개변수 OUT 이 존재하는 프로시저 CREATE 방법)

CREATE OR REPLACE PROCEDURE SP_ORG_TEST (OUT_CODE OUT VARCHAR2)

IS

BEGIN

    OUT_CODE := ‘-1’;

   

    — 내용 기입. INSERT TABLE 어쩌구 저쩌구.

   

    COMMIT;

   

    OUT_CODE := ‘1’;

EXCEPTION

    WHEN OTHERS

    THEN

        — OUT_CODE := ‘0’;

        DBMS_OUTPUT.PUT_LINE (‘SP_ORG_TEST EXCEPTION’);

END SP_ORG_TEST;



2. 프로시저 호출 (매개변수 OUT 이 존재하는 프로시저 SELECT 방법)

* 원래 프로시저는 SELECT 하는 개념이 아니지만, 프로시저 SELECT 로 검색하는 분들을 위해 이렇게 적었다.

 

SET SERVEROUTPUT ON;

DECLARE

    V_RESULT VARCHAR2(200);

BEGIN

    SP_ORG_TEST(V_RESULT);

    DBMS_OUTPUT.PUT_LINE(‘V_RESULT : ‘ || V_RESULT);

END;

위 코드를 실행했을 때, 콘솔에 <PL/SQL 프로시저가 성공적으로 완료되었습니다.> 라고 출력되면 성공이다.

3. 자바에서 프로시저 실행 (매개변수 OUT 이 존재하는 프로시저 SELECT 방법)

public static void main(String[] args) {

    Connection conn = null;
    CallableStatement statement = null;
    String result = “”;
 
    try {
        conn = getConnection();

        statement = conn.prepareCall(“{call SP_ORG_TEST(?)}”);
        statement.registerOutParameter(1, java.sql.Types.VARCHAR);
        statement.executeUpdate();

        result = statement.getString(1);

        System.out.print(“result : ” + result);

    } catch (SQLException e) {
        e.printStackTrace();

    } catch (Exception e) {
        e.printStackTrace();

    } finally {
        try {
            if (statement != null) {
                statement.close();
            }
        } catch (Exception e) {
            statement = null;
        }

        try {
            if (conn != null) {
                conn.close();
            }
        } catch (Exception e) {
            conn = null;
        }
    }

}

위 코드를 실행했을 때, 콘솔에 <result : 1> 이라고 출력되면 성공이다.

[TOMCAT] localhost 접속은 되는데 ip 접속은 안되는 현상 (address=”0.0.0.0″ 추가)

[TOMCAT] localhost 접속은 되는데 ip 접속은 안되는 현상 (address=”0.0.0.0″ 추가)

윈도우 10 환경에서 아파치 없이, 톰캣만 기동하여 개발서버 세팅을 했는데, 외부에서 접속이 되지 않는 현상이 있었다.

(네트워크로 연결된 다른 컴퓨터에서 인터넷 익스플로러를 띄워, 해당 ip 주소를 입력했을 때, 찾지 못하는 현상 발생)

외부에서 접속이 안되는 문제는 둘째 치고,
해당 컴퓨터에서 인터넷 익스플로러를 띄워서 주소창에 localhost 를 입력하면 접속이 되는데,
주소창에 ip 주소를 입력하면 접속이 되지 않았다.

정반대의 경우라면 어떻게 이해를 해보겠는데, 내 상식으로는 이해가 되지 않았다.

localhost 를 입력하는 방식은, 먼저 localhost 를 입력하는 것이 1단계이고,

루프백(Loop-back) 으로 인식하고 내부 ip 주소를 찾아서 접속하는 것이 2단계이다. 이 2단계가 정상 작동하였다.

그런데 정작, 곧바로 ip 주소를 입력하는 1단계 방식이 안된다니 상식적으로 이해가 되지 않았다.

결론적으로 해당 현상은 tomcat5.x 버전 계열의 문제로 보이는데,
tomcat이 기동되고 port를 바인딩할 때 IPv6로 바인딩이 되는 문제이다.

server.xml 에 address=”0.0.0.0″ 어트리뷰트를 추가하여 해결하였다.
ex) <Connector port=”8080″ maxHttpHeaderSize=”8192″ address=”0.0.0.0″ (후략)

결국 톰캣은 IPv6 주소로 떠있었던 것이고,

localhost 접속이 가능했던 이유는, 루프백 결과, IPv6 주소를 찾아서 바인딩시켰기 때문이다.

ip 주소로 접속이 불가능했던 이유는, IPv4 주소를 입력했기 때문이었다.

[TOMCAT] 파일 내부의 한글 깨지는 문제 (Dfile.encoding 추가하여 해결)

[TOMCAT] 파일 내부의 한글 깨지는 문제 (Dfile.encoding 추가하여 해결)

WAS는 TOMCAT이고, 아파치 없이 구동하는 경우였다.

사내 개발서버에서는 문제가 없었는데

고객사 서버에서 문자열이 깨지는 문제가 있었다.

보통 한글 깨지는 문제가 발생하면, Post 로 넘어갈 때는 문자열이 깨지지 않는데, Get 으로 넘어갈 때 문자열이 깨지는 경우다.

그렇다면 server.xml 의 Connector 태그에 URIEncoding=”UTF-8″ 어트리뷰트를 추가하면 된다.

ex) <Connector port=”8080″ protocol=”HTTP/1.1″ connectionTimeout=”20000″ redirectPort=”8443″ />

해당 코드를,

<Connector URIEncoding=”UTF-8″ port=”8080″ protocol=”HTTP/1.1″ connectionTimeout=”20000″ redirectPort=”8443″ />

위 코드와 같이 변경.

그런데 이번 문제는 파라미터가 깨지는 것이 아니라,

파일을 다운로드 받았을 경우 파일 내부의 한글 문자열이 깨지는 문제였다.

이미 Connector 태그에 URIEncoding=”UTF-8″ 이 추가되어 있었고,

Post 든 Get 이든 파라미터도 정상적으로 넘어가는 상황이었다.

멀티파트를 이용해 가져온 바이너리 파일 내부의 한글이 깨지는 문제였다.

실제로 HTML 파일을 다운로드 받아와서 열면, 그 안의 한글이 깨져 나오는 것이다.

해결 방법은 톰캣 카탈리나 옵션에 Dfile.encoding 을 추가하는 것이었다.

catalina.bat 에 아래와 같이 옵션을 추가해주었다.

set CATALINA_OPTS=-Xms1024m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m -Dfile.encoding=”UTF-8″

파일 인코딩이 UTF-8로 수행되어서 인지 더 이상 한글이 깨지지 않았다.

이번은 윈도우 서버여서 catalina.bat 을 수정하였는데, 리눅스라면 톰캣폴더/bin/catalina.sh 파일을 수정하면 된다.

* 관련 게시글 : 톰캣 펌사이즈 설정 (tomcat permsize) – java.lang.OutOfMemoryError (http://blog.naver.com/bb_/221066553152)

[JAVA] String 을 String… 으로 수정했을 뿐인데 No Such Method 발생

[JAVA] String 을 String… 으로 수정했을 뿐인데 No Such Method 발생

자바에서 String 을 String… 으로 고쳐서 코딩하고, 패치를 마쳤는데 No Such Method 에러 발생.

지금 적어두지 않으면 잊어버릴 것 같아 써둠.

결론을 요약하면, String형의 매개변수를 String… 형으로 변경하였을 때,

해당 메서드를 call하는 클래스 모두를 재컴파일하고, 다시 패치해줘야 한다.

자세한 상황은 다음과 같다.

A.java 에 doSomething 이라는 메서드가 있었는데, 이 메서드는 4개의 매개변수가 필요한 메서드였다.

이를테면 다음과 같은 식이다.

public String doSomething(Object a, Object b, Object c, String d) {

    return “temp”;

}

이 메서드를 B.java, C.java, D.java 에서 잘 갖다쓰고 있었고, 개발서버에서도 문제없이 작동되었다.

그러다 마지막 String 매개변수를 여러 개 넘겨주고 싶은 상황이 왔다. 이 부분을 Z.java 라고 하자.

Z.java 에서는 doSomething(obj1, obj2, obj3, “text1”, “text2”); 방식으로 쓰고 싶은 순간이 온 것이다.

유사시에는 doSomething(obj1, obj2, obj3, “text1”, “text2”, “text3”); 식으로 스트링을 3개든 4개든 늘릴 수 있으면 좋겠다고 판단했다.

이 경우 doSomething 메서드의 마지막 매개변수를 Vector나 ArrayList로 바꾸든가, String 배열(String[])로 바꿔 주면 된다.

나는 여기서 String 형을 String… 형으로 바꿨다.

B.java, C.java, D.java 의 코드는 전혀 고치지 않았다.

자바에서는 알다시피 매개변수를 String… 으로 바꾸면 String을 몇 개를 넘겨주든 String 배열로 받는다.

따라서 public String doSomething(Object a, Object b, Object c, String d) 부분을

public String doSomething(Object a, Object b, Object c, String… d) 코드로 변경했으며,

기존에 해당 메서드를 사용하고 있었던 B.java, C.java, D.java 의 코드를 변경할 필요가 없었다.

그냥 Z.java 에서만 스트링을 여러개 붙여서 해당 메서드를 콜했고, 로컬에서는 이상 없이 동작했다.

코드를 직접 변경한 클래스 2개,

즉 A.class(doSomething 메서드가 존재하는 클래스) 와 Z.class(스트링 n개로 doSomething 메서드를 call하는 클래스) 만 패치했다.

그 결과 기존 B.class, C.class, D.class 부분에서 No Such Method 에러가 나는 것이 아닌가.

처음엔 로컬과 개발서버의 자바 버전 차이인 줄 알고(String… 형을 지원하지 않는 버전인가 싶었다) 점검했지만 그게 아니었다.

메서드의 매개변수를 String 형에서 String… 형으로 바꿨다면,

기존에 해당 메서드를 call 하는 모든 클래스를 재컴파일 및 패치해야 하는 것이다.

개발하는 입장에서는 변경된 것이 없었다고 생각했지만,

알고보니 로컬 테스트에서는 이클립스가 B.java, C.java, D.java 에 대해서 자동으로 재컴파일 해줘서, 오류가 발생하지 않았다.

오버로딩과는 동작원리가 다르다.

만약 String 배열을 쓰는 메서드를 하나 더 만들었다면(오버로딩이었다면), B.class, C.class, D.class 를 재컴파일할 이유는 없다.

오버로딩은 불리는 쪽(Callee)에서 메서드를 여러 개 제공하는 것이다.

하지만 String…형은 부르는 쪽(Caller)에서 컴파일 과정에서 메서드에 맞는 자바 바이트 코드를 생성해내는 것으로 보인다.

String… 형을 사용한 부분인 A.class 는 해당 매개변수를 무조건 배열로만 받고,

B.class, C.class, D.class 쪽에서 스트링 1개를 배열에 담아 call하는 바이트 코드가 생성되는 것으로 보인다.

소스 코드는 변경되지 않았는데, 바이트 코드가 변경되는 특이한 상황이었던 것 같다. 

jsImgSlider (js image slide / javascript js image slide / js 그림 슬라이드 / javascript 그림 슬라이드)

jsImgSlider (js image slide / javascript js image slide / js 그림 슬라이드 / javascript 그림 슬라이드)

js 그림 슬라이드를 도와주는 라이브러리와 예제 소스.

너무 좋다…

갖다 쓰기 정말 쉽고 잘 만들었다…

관련링크 : http://www.menucool.com/javascript-image-slider

[자바스크립트] charcode 32와 160 차이 (javascript char 160 to 32)

[자바스크립트] charcode 32와 160 차이 (javascript char 160 to 32)

charcode 32는 공백(Space)이고, 160은 NBSP(non-breaking space) 이다.

두 가지 모두 공백이지만, 같은 소스코드의 페이지라도 브라우저에 따라 컴포넌트 내의 문자열을 서로 다르게 가져온다. (ex: textarea 에서 문자열을 가져올 때)

익스플로러는 32로만 가져오는데, 크롬은 32와 160을 섞어서 가져오는 경우가 많은듯 하다.

따라서 두 가지 공백을 하나로 통일해주는 코드가 필요하다.

구글링 결과 크게 다음 3가지 코드가 있다.

(1) 32와 160을 모두 공백(32)으로 변경
return _text.replace(/\s+/g, ” “);
   
(2) 160만 공백(32)으로 변경
return _text.replace(new RegExp(String.fromCharCode(160),”g”), ” “);
   
(3) 160만 공백(32)으로 변경
return _text.replace(/\xA0/g, ” “);

다음은 직접 작성한 예제소스.

로컬에 html 파일로 저장해서 실행하면 된다.

<html>
<head>
<script>
window.onload = function() {
    doTest();
}

function doTest() {
    alert(“doTest”);
   
    var beforeText = “대” + String.fromCharCode(32) + “학” + String.fromCharCode(160) + “교”;
    var afterText = replaceNbsp(beforeText);
   
    alert(“BEFORE : ” + getCharCodeFromText(beforeText));
    alert(“AFTER : ” + getCharCodeFromText(afterText));
}

// 문자열을 캐릭터코드로 변경해서 리턴
function getCharCodeFromText(_text) {
    if (_text == null || _text == “”) {
        return “empty”;
    }

    var result = “”;
    var len = _text.length;
    for (var i=0; i<len; i++) {
        if (result.length > 0) {
            result += “/”;
        }
        result += _text.charCodeAt(i);
    }
   
    return result;
}

function replaceNbsp(_text) {
    if (_text == null || _text == “”) {
        return “”;
    }

    // (1) 32와 160을 모두 공백(32)으로 변경
    // return _text.replace(/\s+/g, ” “);
   
    // (2) 160만 공백(32)으로 변경
    return _text.replace(new RegExp(String.fromCharCode(160),”g”), ” “);
   
    // (3) 160만 공백(32)으로 변경
    // return _text.replace(/\xA0/g, ” “);
}
</script>
</head>
<body>
</body>
</html>

[JAVA] Base64 인코딩 (Base64Encode) / Base64 디코딩 (Base64Decode)

[JAVA] Base64 인코딩 (Base64Encode) / Base64 디코딩 (Base64Decode)

* 필요 라이브러리

commons-codec-1.8.jar 파일을 임포트 하였다.

(org.apache.commons.codec.binary.Base64 클래스를 사용하기 위함)

* 예제 소스코드

package com.bb.test;

import java.nio.charset.Charset;

import org.apache.commons.codec.binary.Base64;

public class Base64Test {

    public static void main(String[] args) {
       
        Base64Test base64Test = new Base64Test();
       
        try {
            String originStr = “테스트 dbcdsfqad 문자열ㅇㅇ”;
            String encodeStr = base64Test.base64Encode(originStr, “EUC-KR”);
            String decodeStr = base64Test.base64Decode(encodeStr, “EUC-KR”);
           
            System.out.println(“originStr : ” + originStr);
            System.out.println(“encodeStr : ” + encodeStr);
            System.out.println(“decodeStr : ” + decodeStr);
           
        } catch (Exception e) {
            e.printStackTrace();
        }
       
        System.out.println(“———-“);
       
        try {
            String originStr2 = “테스트 dbcdsfqad 문자열ㅇㅇ”;
            String encodeStr2 = base64Test.base64Encode(originStr2, “UTF-8”);
            String decodeStr2 = base64Test.base64Decode(encodeStr2, “UTF-8”);
           
            System.out.println(“originStr2 : ” + originStr2);
            System.out.println(“encodeStr2 : ” + encodeStr2);
            System.out.println(“decodeStr2 : ” + decodeStr2);
       
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
   
    public String base64Encode(String str, String charset) throws Exception {
        byte[] bytes = str.getBytes(charset);
        String result = Base64.encodeBase64String(bytes);
        return result;
    }
   
    public String base64Decode(String str, String charset) throws Exception {
        byte[] bytes = Base64.decodeBase64(str);
        String result = new String(bytes, Charset.forName(charset));
        return result;
    }
}

* 예제 소스코드 실행결과

originStr : 테스트 dbcdsfqad 문자열ㅇㅇ
encodeStr : xde9usauIGRiY2RzZnFhZCC5rsDav62kt6S3
decodeStr : 테스트 dbcdsfqad 문자열ㅇㅇ
———-
originStr2 : 테스트 dbcdsfqad 문자열ㅇㅇ
encodeStr2 : 7YWM7Iqk7Yq4IGRiY2RzZnFhZCDrrLjsnpDsl7TjhYfjhYc=
decodeStr2 : 테스트 dbcdsfqad 문자열ㅇㅇ

[JAVA] Byte to String / String to Byte

[JAVA] Byte to String / String to Byte

* Byte to String

String result = new String(bytes);

또는

String result = new String(bytes, Charset.forName(charset));

ex 1) String result = new String(bytes, Charset.forName(“UTF-8”));

ex 2) String result = new String(bytes, Charset.forName(“EUC-KR”));

* String to Byte

byte[] bytes = str.getBytes();

또는

byte[] bytes = str.getBytes(charset);

ex 1) byte[] bytes = str.getBytes(“UTF-8”);

ex 2) byte[] bytes = str.getBytes(“EUC-KR”);

———-

2020. 02. 27(목) 내용 추가.

위 내용처럼 Byte to String 을 수행해도 되지만, 스트링이 깨져나오는 현상이 있었다.

byte[] 배열의 내용을 for문을 이용해서 찍어보니 마이너스 값이 많이 찍혀나왔다.

특히 String 을 다시 byte 로 되돌려도 문자열이 정상적으로 복원되지 않았다.

이런 경우 Base64 를 이용해서 Byte to String 과 String to Byte 를 수행하면 문자열이 깨지지 않고 변환되었다.

* Byte to String

String result = Base64.encodeBase64URLSafeString(bytes);

* String to Byte

byte[] bytes = Base64.decodeBase64(str);

Base64 는 아래 패키지를 임포트하면 사용할 수 있다.

import org.apache.commons.codec.binary.Base64;

printError (jsp 파일에서 StackTrace 내용 출력)

printError (jsp 파일에서 StackTrace 내용 출력)

<%!
    public void printError(JspWriter out, Exception e) throws Exception {
        // java.lang.ArithmeticException: / by zero
        out.println(e.getClass().getName() + “: ” + e.getMessage() + “<br>”);

   

        StackTraceElement[] trace = e.getStackTrace();   

        for (int i = 0; i < trace.length; i++) {
            StackTraceElement stack = trace[i];
            String str = stack.toString();
   
            // at Tester.aa(Tester.java:23)
            // at Tester.main(Tester.java:8)
            out.println(“at ” + str + “<br>”);   

        }
    }
%>

또는

public String getStackTraceText(Exception e) {
if (e == null) {
return "";
}

StringBuffer buff = new StringBuffer();

// java.lang.ArithmeticException: / by zero
buff.append(e.getClass().getName() + ": " + e.getMessage() + "\n");

StackTraceElement[] trace = e.getStackTrace();
if (trace != null && trace.length > 0) {
for (int i = 0; i < trace.length; i++) {
StackTraceElement stack = trace[i];
String str = stack.toString();

// at Tester.aa(Tester.java:23)
// at Tester.main(Tester.java:8)
buff.append("at " + str + "\n");
}
}

return buff.toString();
}

[APACHE TOMCAT] 아파치톰캣 한글깨짐 / 한글 파라미터 깨지는 문제

[APACHE TOMCAT] 아파치톰캣 한글깨짐 / 한글 파라미터 깨지는 문제

아파치 httpd 웹서버를 통하면 한글 파라미터가 깨지는 현상이 있었다.

문제는 톰캣으로 접근하면 한글이 깨지지 않는데,

아파치 httpd를 통해서 접근했을 때는 한글 파라미터가 깨졌다.

특히 한글 파라미터를 encodeURIComponent 함수를 통해 UTF-8로 encode하여 URL에 붙여줬는데도 이러한 현상이 발생했다.

아파치를 거쳐가면 한글이 깨지고, 곧바로 톰캣으로 들어가면 한글이 깨지지 않으니

아파치 쪽이 잘못되었다고 생각해서 계속 httpd.conf를 고치려 했는데 문제는 톰캣에 있었다.

 

톰캣 폴더 하위의 conf 폴더 내의 server.xml 을 보면 톰캣 커넥터가 2개 존재한다.

Connector 태그가 2개 있다는 뜻이다.

첫째로

<Connector connectionTimeout=”20000″ port=”9090″ protocol=”HTTP/1.1″ redirectPort=”9443″/>

<Connector connectionTimeout=”20000″ port=”9090″ protocol=”HTTP/1.1″ redirectPort=”9443″ URIEncoding=”UTF-8″/>

로 수정해야 하며,

둘째로

<Connector port=”9009″ protocol=”AJP/1.3″ redirectPort=”9443″/>

<Connector port=”9009″ protocol=”AJP/1.3″ redirectPort=”9443″ URIEncoding=”UTF-8″/>

로 수정해야 한다.

첫번째 부분을 수정하지 않으면 Get방식으로 한글 파라미터를 넘길 때, 톰캣 단에서 한글이 깨지게 된다.

두번째 부분은 AJP 프로토콜 관련인데 AJP 프로토콜은 아파치와 톰캣을 이어주는 포트이다.

따라서 두번째 부분을 수정하지 않으면 Get방식으로 한글 파라미터를 넘길 때, 아파치를 통해 톰캣으로 넘어가는 과정에서 한글이 깨지게 된다.

필자의 경우 첫번째 부분만 고쳐두었기 때문에 톰캣으로 접근할 때는 문제가 없었고,

아파치를 통해 톰캣으로 접근할 때만 문제가 발생한 것이다.

우분투에서 아파치 웹서버 httpd 와 톰캣 연동 (ubuntu 에서 apache 웹서버 httpd 와 tomcat 연동)

우분투에서 아파치 웹서버 httpd 와 톰캣 연동 (ubuntu 에서 apache 웹서버 httpd 와 tomcat 연동)

먼저 톰캣과 아파치 웹서버 httpd 가 이미 설치되어 있다는 가정 하에 진행한다.

설치되어 있지 않다면 아래 문서를 보고 설치하자.

* 톰캣7 설치 : ubuntu에 tomcat7 설치 (http://blog.naver.com/bb_/221330415361)

* 아파치 httpd 설치 : 우분투에서 아파치 웹서버 httpd 설치 (http://blog.naver.com/bb_/221422963830)

1. 버전 확인
참고삼아 톰캣과 httpd의 버전을 명시한다.

tomcat 버전 : 7.0.90 (apache-tomcat-7.0.90.tar.gz) / 주소 : http://tomcat.apache.org/download-70.cgi

apache httpd 버전 : 2.2.21 (httpd-2.2.21.tar.gz) / 주소 : http://archive.apache.org/dist/httpd/

2. mod_jk 다운로드

톰캣과 아파치 웹서버 httpd 를 연동하기 위해서는 mod_jk 라는 모듈이 필요하다.

apache httpd 2.2.21 버전과 연동 성공한 mod_jk 버전은 1.2.31 버전이다. (첨부파일 참고)

mod_jk 버전 : 1.2.31 (mod_jk-1.2.31-httpd-2.2.x.so) / 주소 : http://archive.apache.org/dist/tomcat/tomcat-connectors/jk/binaries/linux/jk-1.2.31/x86_64/

버전에 맞는 mod_jk 의 so 파일을 구해서, 적용시키기 원하는 httpd 의 modules 폴더에 넣으면 된다.

ex) /home/hdwk/httpd/modules 폴더 내에 mod_jk-1.2.31-httpd-2.2.x.so 파일을 넣는다.

* 적절한 mod_jk so 파일을 얻기 위해서는 운영체제 비트수가 필요한데, 요새 거의 64비트이지만 혹시 모르니 확인해본다.

리눅스에서 비트수를 알아내는 명령어는 여러가지가 있지만 참고로 다음의 명령어들이 있다.

ex 1) 리눅스에서 getconf LONG_BIT 입력시 결과 => 64

ex 2) 리눅스에서 arch 입력시 결과 => x86_64

3. 톰캣의 AJP 포트 확인

톰캣의 conf 폴더에 들어있는  server.xml 파일에서 AJP 포트를 확인한다.

* AJP란 Apache Jserv Protocol 의 약자로, 웹서버와 어플리케이션 서버를 연동하기 위해 고안된 프로토콜이라고 생각하면 된다.

참고로 여기서 웹서버는 아파치 httpd 이고, 톰캣은 어플리케이션 서버이다.

server.xml 파일에서 AJP를 검색하면

<Connector port=”8009″ protocol=”AJP/1.3″ redirectPort=”8443″/>

이와 같은 부분이 나온다.

여기서 “8009”가 AJP 포트이다. 기억해둔다.

(톰캣 설정에 따라 값이 다를 수 있으니 꼭 확인한다)

4. workers.properties 파일 작성

httpd 의 conf 폴더 안쪽에 workers.properties 파일을 새로 작성한다.

cd /home/hdwk/httpd/conf

vi workers.properties

내용은 아래와 같다.

worker.list=worker1

#AJP1.3 프로토콜 사용
worker.worker1.type=ajp13
#톰캣이 돌고 있는 호스트
worker.worker1.host=localhost
#톰캣의 포트번호
worker.worker1.port=8009

제일 마지막 worker.worker1.port 바로 이 부분에 톰캣 server.xml 에서 봤던 AJP 포트를 기입해야 한다.

5. httpd.conf 수정

httpd 의 conf 폴더 안의 httpd.conf 파일을 수정한다.

cd /home/hdwk/httpd/conf

vi /home/hdwk/httpd/conf

LoadModule 로 검색하면 주석처리된 # LoadModule 어쩌고 저쩌고 나올 것이다.

그 바로 아래에 다음과 같이 기입한다.

LoadModule jk_module modules/mod_jk-1.2.31-httpd-2.2.x.so

# tomcat connection
<IfModule jk_module>
JkWorkersFile conf/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel info
JkLogStampFormat “[%a %b %d %H:%M:%S %Y]”
JkShmFile logs/mod_jk.shm
JkMount /*.do worker1
</IfModule>

so 파일명을 실제와 틀리게 쓰면 곤란하니 정확하게 쓰자.

이렇게 하면 [http://도메인주소:포트번호/파일명] 으로 접근했을 때, 파일명이 *.do 패턴에 일치할 경우 톰캣에 요청을 보내 응답을 돌려받아주고, 패턴에 일치하지 않을 경우 톰캣을 거치지 않고 httpd 웹서버에서 파일 시스템의 파일을 곧바로 전달해준다. (ex: gif, jpg, png 등)

6. httpd 웹서버 재기동

httpd 웹서버를 재기동한다.

cd /home/hdwk/httpd/bin

./apachectl stop

./apachectl start

하거나,

cd /home/hdwk/httpd/bin

./apachectl restart

하면 된다.

이제 파일명 패턴에 따라 잘 매핑되는지 (*.do 일 경우에는 톰캣을 타고 그렇지 않으면 웹서버 단에서 다 처리하는지) 확인하면 된다.

끝!

우분투에서 아파치 웹서버 httpd 설치 (ubuntu 에서 apache 웹서버 httpd 설치)

우분투에서 아파치 웹서버 httpd 설치 (ubuntu 에서 apache 웹서버 httpd 설치)

0. 파일 다운로드
인터넷에서 httpd-2.2.21.tar.gz 찾아서 다운로드
cf) http://archive.apache.org/dist/httpd/ 

1. FTP 로 작업 디렉토리 위치에 httpd-2.2.21.tar.gz 업로드

예를 들면 /home/hdwk 위치에 httpd-2.2.21.tar.gz 업로드


2. root 로 접속
putty로 접속하거나 su – root

3. 작업 디렉토리를 특정 사용자 권한으로 변경 (여기서는 사용자명 hdwk)
cd /home/hdwk
chown -R hdwk:hdwk *
chmod -R 755 *

4. 특정 사용자로 접속 (여기서는 사용자명 hdwk)
su -hdwk

5. httpd 압축파일 해제하고 폴더 이름을 httpd로 변경
tar -xvf httpd-2.2.21.tar.gz
mv httpd-2.2.21 httpd

6. httpd 폴더 안으로 이동 후, configure 입력
cd httpd
./configure –prefix=작업디렉토리/httpd –enable-module=so –enable-shared=max 입력
ex) ./configure –prefix=/home/hdwk/httpd –enable-module=so –enable-shared=max

7. make 로 컴파일
과정 완료되면 make 라고 입력 (컴파일 실행됨)

8. 설치
make과정이 끝나면 make install 이라고 입력 (설치 시작)

9. vi 로 httpd 설정파일 수정
cd conf
vi httpd.conf

9-1. Listen 포트를 원하는 값으로 변경
ex) Listen 80
80 일 경우 80 포트(포트 입력하지 않아도) 접근가능하다는 뜻.
바꾸고 싶다면 변경
예를 들면, Listen 80 을 Listen 30000 으로 변경

9-2. DocumentRoot 기억
ex) DocumentRoot “/home/hdwk/httpd/htdocs”

9-3. Directory 태그 (첫번째 Directory 태그 무시하고, 두번째 Directory 태그 볼것) 기억하기.

이곳이 웹페이지가 있는 디렉토리 경로라고 보면 됨.
원한다면 변경해도 됨.
ex) <Directory “/home/hdwk/httpd/htdocs”>

10. httpd 실행
bin 폴더로 접근해서 실행 명령어 입력

cd /home/hdwk/httpd/bin
./apachectl start 를 입력

11. 도메인 주소로 접속
http://도메인주소:30000/ 접속하면
It works! 라고 나와야 함.

이제
http://도메인:30000/ 접속하면 Directory 폴더의 index.htm 를 불러오면 정상
http://도메인:30000/test1.png 접속하면 Directory 폴더의 test1.png 를 불러오면 정상
http://도메인:30000/temp/test2.png 접속하면 Directory 폴더 하위 temp 폴더의 test2.png 를 불러오면 정상임.

설치 끝!

[Spring4] server.xml 추가세팅, servlet-context.xml 추가세팅

[Spring4] server.xml 추가세팅, servlet-context.xml 추가세팅

1. server.xml 추가세팅

server.xml 의 129라인
<Context docBase=”BBWIKI” path=”/wiki” reloadable=”true” source=”org.eclipse.jst.jee.server:BBWIKI”/></Host>

<Context docBase=”BBWIKI” path=”/” reloadable=”true” source=”org.eclipse.jst.jee.server:BBWIKI”/></Host>
로 변경

=> 이제 http://localhost:8080/wiki 라고 치지 않고, http://localhost:8080/ 라고 치면 value = “/” 로 @RequestMapping 됨

2. servlet-context.xml 추가세팅
servlet-context.xml 의 16라인
<resources mapping=”/resources/**” location=”/resources/” />

<resources mapping=”/css/**” location=”/resources/css/” />
<resources mapping=”/images/**” location=”/resources/images/” />
<resources mapping=”/js/**” location=”/resources/js/” />
<resources mapping=”/resources/**” location=”/resources/” />
로 변경

=> 이제,
http://localhost:8080/resources/js/temp.js 라고 치면 파일시스템상 /src/main/webapp/resources/js/temp.js 파일을 가져옴
또는
http://localhost:8080/js/temp.js 라고 치면 파일시스템상 /src/main/webapp/js/temp.js 파일을 가져옴

3. servlet-context.xml 추가세팅 두번째

servlet-context.xml 의 20라인
<beans:property name=”prefix” value=”/WEB-INF/views/” />
<beans:property name=”suffix” value=”.jsp” />

<beans:property name=”prefix” value=”/WEB-INF/views/” />
<beans:property name=”suffix” value=”” />
로 변경

=> 이제 @Controller 에서 return “home”; 이 아닌 return “home.jsp”; 해야 jsp 파일을 찾아감.

4. 스프링 리스폰스 바디의 한글값이 물음표로 깨질 경우

@ResponseBody
@RequestMapping(value = “/wiki/doc/write_req.do”, method = {RequestMethod.GET, RequestMethod.POST})

@ResponseBody
@RequestMapping(value = “/wiki/doc/write_req.do”, produces=”application/text; charset=UTF8″, method = {RequestMethod.GET, RequestMethod.POST})

로 변경.

단순 문자열이므로 produces=”application/text; charset=UTF8″ 를 추가한 것임.
(json 형태로 내려줄 경우 produces=”application/json; charset=UTF8″ 를 추가해야 함)

[Spring4] @RestController와 @Controller의 차이

[Spring4] @RestController와 @Controller의 차이

@RestController는 각 메서드에 @ResponseBody를 붙일 필요없는 (ResponseBody를 기본으로 지원하는) Controller이다.

view가 필요하지 않고 RestAPI만 제공하고 싶은 경우 사용한다.

호출자(서버 응용 프로그램이 아닌 서버)가 사용될 수 없어서 사라졌습니다.

호출자(서버 응용 프로그램이 아닌 서버)가 사용될 수 없어서 사라졌습니다.

아래와 같은 자바스크립트 오류 메시지가 발생했다.

“호출자(서버 응용 프로그램이 아닌 서버)가 사용될 수 없어서 사라졌습니다. 모든 연결이 올바르지 않습니다. 호출이 실행되지 않았습니다.”

값이 아닌 참조 타입의 객체/배열의 링킹이 끊긴 경우 발생하는 오류이다.

대표적으로 window.open()으로 열기한 팝업과 관련된 오류를 예로 들 수 있다.

팝업에서 생성한 객체 또는 배열변수를 여는 쪽 윈도우(window.open()을 실행한 윈도우, 즉 opener)에서 가져다 쓰려고 할 때,

만약 팝업이 닫혀 있다면 참조값을 찾을 수 없어서 오류가 발생한다.

자바스크립트 문자열(String)과 숫자(Number)는 native 타입이어서 팝업이 닫혀 링킹이 끊겨도 상관없다(값 복사).

그러나 객체(Object)와 배열(Array)은 참조 상태(개념적으로 보면 일종의 포인터만 갖고 있는 상태)이므로 팝업이 닫히면 곤란하다.

해결책 1. 문자열 값으로 주고받기

오류를 원천적으로 없애려면 문자열로 주고받는 게 가장 확실하다.

문자열 복사는 어떤 브라우저에서도 값 복사로 이뤄지기 때문이다.

팝업창의 예를 들면 window.open() 으로 열기한 팝업 창에서는 객체/배열을 stringify 처리해서 문자열로 넘겨주고, opener 창에서는 문자열을 parse 해서 다시 객체/배열로 만들어 써야 확실하다.

팝업창 처리 예시

var returnValue = JSON.stringify(something);
window.opener.callbackFunc(returnValue);


window.close();

opener창 처리 예시

function callbackFunc(_returnValue) {

    var returnObj = JSON.parse(_returnValue);

    console.log(“returnObj”, returnObj);
}

해결책 2. 깊은 복사(Deep Copy) 활용하기

아니면 팝업창이 닫히기 전에 opener 창에서 깊은 복사(Deep Copy)를 수행하는 방법이 있다.

팝업창 처리 예시

var returnObj = something;

window.opener.callbackFunc(returnObj);

window.close();

opener창 처리 예시

function callbackFunc(_returnObj) {

    var returnObj = deepCopy(_returnObj);

    console.log(“returnObj”, returnObj);
}

deepCopy 함수의 내용은 깊은 복사를 수행하는 내용이면 된다.
객체/배열을 복사해서 새로운(기존과 독립인) 새로운 객체/배열을 만드는 것이다.

예를 들면 아래와 같이 짤 수 있다.

* JSON 객체를 사용한 깊은 복사 (객체/배열 복제) 함수


// js 깊은 복사
function deepCopy(_obj) {
    if (_obj == null) {
        return null;
    }

    return JSON.parse(JSON.stringify(_obj));
}

깊은 복사에 대해서는 다음 포스트에 더 정리해두었다. => https://blog.naver.com/bb_/222381553736

vue.js 관련 메모

vue.js 관련 메모

1. URL에 #[샵]이 포함되면 # 앞쪽은 URL로 인식되고, # 뒤쪽은 데이터로 인식한다.
(브라우저에는 반영되어 클라이언트 js에서 데이터를 가져다 쓸 수는 있지만, 페이지 새로고침은 일어나지 않는다.)

2. 샵(#) 대신 해시뱅(#!) 으로 쓰기도 한다.
ex) http://도메인/#!/main/login

3. 참고 단어 모음
– SPA (Single Page Application)

– Two-way binding (양방향 연결) : Angular 2로부터 차용. View 과 Model 의 관계에서, View 의 데이터를 변경하면 Model 에 반영되고, Model 의 데이터를 변경하면 View 에 반영되는 것.

– 라우트 (Route) : 라우트 객체(VueRouter)에 컴포넌트 명을 push 하면, router-view 태그 영역(<router-view></router-view>)이 해당 컴포넌트의 내용으로 바뀐다.

– 중첩된 라우트 (Nested routes) : 기존 라우트 객체(VueRouter)에 children 라우트를 추가하고, 해당하는 Vue 파일 내부에 router-view 태그를 추가하면 액자식으로 작동한다.

<엑셀VBA 입문 17강> 변수를 같은 이름으로 사용하면 섞일까? (지역변수와 전역변수)

<엑셀VBA 입문 17강> 변수를 같은 이름으로 사용하면 섞일까? (지역변수와 전역변수)

서로 다른 함수에서 동일한 이름의 변수를 사용할 때 어떻게 될까요?

어떤 분이 질문해주셔서 써봅니다.

프로그래밍에서는 각각의 함수 안에서만 사용하는 변수를 [지역변수] 라고 합니다.

여러 함수가 공통으로 사용하는 변수를 [전역변수] 라고 합니다.

이는 <변수를 어디에 선언하느냐>에 따라 결정되는데요.

Function 블록 (Function ~ End Function) 안쪽에 [Dim 변수명]이 존재한다면 지역변수(함수마다 달리 사용)가 됩니다.

함수 바깥쪽(소스코드 최상단)에 Dim 변수명이 존재한다면 전역변수(여러 함수 공통으로 사용)가 됩니다.

1. 지역변수 예제

지역변수 예제는 다음과 같습니다.

Func1 함수와 Func2 함수를 차례로 호출하는 매크로입니다.

Sub Macro1()

‘ Macro1 Macro

‘ 바로 가기 키: Ctrl+k

Call Func1
Call Func2

End Sub

‘——————–

Function Func1()

‘함수 안쪽에 변수 선언
Dim aa

aa = aa + 1
MsgBox (“첫번째 값 : ” & aa)

aa = aa + 1
MsgBox (“두번째 값 : ” & aa)

End Function

‘——————–

Function Func2()

‘함수 안쪽에 변수 선언
Dim aa

aa = aa + 1
MsgBox (“세번째 값 : ” & aa)

aa = aa + 1
MsgBox (“네번째 값 : ” & aa)

End Function

위 매크로의 실행결과는 1, 2, 1, 2 순으로 메시지가 표시되게 됩니다.

 

함수마다 Dim aa 로 변수를 새로 설정해줬으므로, 함수 안에서 사용하고, 함수가 종료되면서 해당 변수는 버려지게 됩니다.

일회용이라고 생각하시면 될듯 합니다.

결과적으로 다시 실행해도 1, 2, 1, 2 가 표시됩니다.

2. 전역변수 예제

다음으로 전역변수의 예입니다.

지역변수 예제를 기본으로 하지만, 이번에는 함수 바깥쪽(최상단)에 변수를 선언해보겠습니다.

‘함수 바깥쪽(최상단)에 변수 선언. 즉, 전역변수로 선언.
Dim aa


Sub Macro1()

‘ Macro1 Macro

‘ 바로 가기 키: Ctrl+k

Call Func1
Call Func2

End Sub

‘——————– 

Function Func1()

aa = aa + 1
MsgBox (“첫번째 값 : ” & aa)

aa = aa + 1
MsgBox (“두번째 값 : ” & aa)

End Function

‘——————– 

Function Func2()

aa = aa + 1
MsgBox (“세번째 값 : ” & aa)

aa = aa + 1
MsgBox (“네번째 값 : ” & aa)

End Function

이번 매크로의 실행결과는 1, 2, 3, 4 순으로 메시지가 표시되게 됩니다.

최상단에 선언된 Dim aa 를 여러 함수가 공유하고 있습니다. 또한 함수가 종료되어도 해당 변수는 버려지지 않고 보존됩니다.

결과적으로 처음 실행하면 1, 2, 3, 4 가 표시되고,

다시 실행하면 5, 6, 7, 8 이 표시되고,

또 다시 실행하면 9, 10, 11, 12 가 표시됩니다.

3. 지역변수와 전역변수의 혼용(같이 사용)

별로 추천하지 않는 방법이지만, 지역변수와 전역변수를 같이 사용할 수 있습니다.

예를 들면, Func1 함수는 전역변수를 사용하고, Func2 함수는 지역변수를 사용할 수 있습니다.

예제코드는 아래와 같습니다.

‘함수 바깥쪽(최상단)에 변수 선언. 즉, 전역변수로 선언.
Dim aa


Sub Macro1()

‘ Macro1 Macro

‘ 바로 가기 키: Ctrl+k

Call Func1
Call Func2

End Sub

‘——————– 

Function Func1()

aa = aa + 1
MsgBox (“첫번째 값 : ” & aa)

aa = aa + 1
MsgBox (“두번째 값 : ” & aa)

End Function

‘——————– 

Function Func2()

‘Func2 함수만 함수 안쪽에 변수 선언. 즉, 지역변수로 선언.
Dim aa

 

aa = aa + 1
MsgBox (“세번째 값 : ” & aa)

aa = aa + 1
MsgBox (“네번째 값 : ” & aa)

End Function

이번 매크로의 실행결과는 1, 2, 1, 2 순으로 메시지가 표시되게 됩니다.

다시 실행하면 3, 4, 1, 2 가 표시되고,

또 다시 실행하면 5, 6, 1, 2 가 표시됩니다.

4. VBA 변수 선언시 주의사항

VB/VBA 는 사실 변수를 선언하지 않아도([Dim 변수명] 코드를 쓰지 않아도) 변수를 사용할 수 있습니다.

다른 프로그래밍 언어와의 차이점입니다.

(예를 들어, C, C++, JAVA 에서는 변수 선언 없이 변수를 사용하면 오류가 발생합니다.)

VBA 에서는 변수 선언 없이 변수를 쓰면 그냥 지역변수로 인식됩니다.

하지만 이 방법은 권장하지 않습니다.

처음에 변수를 선언하지 않고 사용했을 때는 자동으로 지역변수로 처리되니 편하고 좋은데,

오랜 시간이 지나 이 사실을 깜빡하고 이미 사용했던 변수명을 전역변수로 선언한다면,

지금까지 지역변수로 동작했던 변수들이 전역변수화 되면서 생각하지 못한 버그가 발생하게 됩니다.

그러므로 지역변수를 꼭 [Dim 변수명]으로 선언 후 사용해주시기 바랍니다.

이어지는 글 <엑셀VBA 입문 18강> indexOf 함수의 확장 https://blog.naver.com/bb_/221650929713

[TortoiseSVN] SVN Revision 되돌리는 법

[TortoiseSVN] SVN Revision 되돌리는 법

Subversion에서 과거의 특정 리비전으로 되돌리는 법. TortoiseSVN을 사용해서 해보았다.

1. SVN 연결된 폴더 위에서 마우스 우클릭 – [TortoiseSVN] – [Show log] 를 클릭

 

2. Log 창에서 롤백하기를 원하는 특정 리비전(Revision) 행을 클릭하고, 마우스 우클릭하여 [Revert to this revision] 클릭하면 특정 리비전 상태로 되돌아간다. 단, 로컬 상으로만 되돌아간 것이지 SVN 쪽 히스토리가 바뀌는 것은 아니다.

만약 로컬 파일 뿐만 아니라 SVN 히스토리상 특정 리비전으로 되돌리고 싶다면, 되돌린 상태의 로컬 파일들을 커밋해주면 된다.

[TortoiseSVN] TortoiseSVN is a shell extension.

[TortoiseSVN] TortoiseSVN is a shell extension.

흔히 “똘똘이”, “톨토이즈”라고 부르는 TortoiseSVN (실제 발음은 놀랍게도 토터스SVN) 을 실행했을 때 아래와 같은 메시지가 뜬다.

TortoiseSVN is a shell extension.
That means it is integrated into the Windows explorer.
To use TortoiseSVN please open the explorer and right-click on any folder you like
to bring up the context menu where you will find all TortoiseSVN commands.
And read the manual!

이를 해석해보면 다음과 같다.

TortoiseSVN은 쉘 확장 프로그램입니다.
이는 윈도우 탐색기에 통합되었음을 의미합니다.
TortoiseSVN을 사용하려면 탐색기를 열고 원하는 폴더를 마우스 오른쪽 버튼으로 클릭하세요.
모든 TortoiseSVN 명령을 찾을 수 있는 컨텍스트 메뉴가 나타납니다.
그리고 매뉴얼을 읽으세요!

쓰여진 내용 그대로, TortoiseSVN 을 실행하기 위해서는 exe 파일을 실행하는 것이 아니라,

특정 폴더 위에서 마우스 우클릭하여 TortoiseSVN 메뉴를 선택하며 실행해야 한다.

[ORACLE] ORDER BY 하는 컬럼에 (NULL)이 있을 경우

[ORACLE] ORDER BY 하는 컬럼에 (NULL)이 있을 경우

ORACLE 기준 NULL은 가장 마지막에 위치한다.

예를 들어 COL 이라는 컬럼이 있고, 값으로 A, B, (NULL)이 있을 경우

SELECT ~ ORDER BY COL ASC;
로 정렬하면 A, B, (NULL) 순이 된다.

SELECT ~ ORDER BY COL DESC;
로 정렬하면 (NULL), B, A 순이 된다.

리눅스에서 윈도우로 가져온 파일에 개행(엔터) 2번씩 들어갔을 경우

리눅스에서 윈도우로 가져온 파일에 개행(엔터) 2번씩 들어갔을 경우

리눅스에서 윈도우로 가져온 소스파일 모든 행에 엔터가 2번씩 들어가는 경우가 있다.

리눅스의 개행 문자는 라인 피드(\n)이고, 윈도우의 개행 문자는 캐리지 리턴과 라인 피드(\r\n)이다.

(참고로 맥킨토시의 개행문자는 캐리지 리턴(\r)이다.)

기존 개행이 \n 인 파일에, 윈도우 개행 문자인 \r\n 이 추가로 들어간 경우다.

다시 말해, \n 이 \r\n\n 또는 \n\r\n 이 된 경우다.

해결 방법은 다음과 같다.

1. 해당 파일을 notepad++로 열기한다.

2. \r\n 을 모두 제거한다. (찾아바꾸기로 \r\n 을 빈값으로 바꾸기)

3. \n 을 \r\n 으로 모두 바꾼다. (찾아바꾸기로 \n 을 \r\n 으로 바꾸기)

[자작VBA함수] VBA 열선택을 돕는 함수 : selectCol

[자작VBA함수] VBA 열선택을 돕는 함수 : selectCol

‘사용 예)

‘첫번째 시트의 H11 셀부터 한 줄씩 내려가면서, 값이 존재하는 마지막 셀까지 영역 선택하기

‘만약 H3000 까지 값이 들어있다면, H11부터 H3000 까지 영역 선택

call selectCol(1, “H11”)

‘//////////////////////////////////////////////////

‘selectCol 함수 : 시트 넘버, 시작할 셀 주소를 넘기면, 시작 셀부터 한 줄씩 내려가면서, 값이 존재하는 마지막 셀까지 영역 선택한다.
Function selectCol(sheetNum, beginCellAddr)

Dim tempRow
Dim tempCol

Dim endCellAddr
Dim emptyCount
Dim loopCount

 

‘sheetNum 번째 시트 활성화
Sheets(sheetNum).Activate

 

‘초기값 지정
‘ex) beginCellAddr == “H11”
tempRow = Range(beginCellAddr).Row
tempCol = Range(beginCellAddr).Column
endCellAddr = Range(beginCellAddr).Address

 

‘beginCellAddr 셀부터 한 줄씩 내려가면서 끝을 검사
Do
    ‘빈칸인지 검사
    If Cells(tempRow, tempCol).Value = “” Then
        emptyCount = emptyCount + 1
    Else
        emptyCount = 0
        endCellAddr = Cells(tempRow, tempCol).Address
    End If
   
    ‘빈칸이 10번 이상 반복되면 종료되었다고 판단. 빠져나간다.
    If emptyCount > 10 Then
        Exit Do
    End If
   
    loopCount = loopCount + 1
    If loopCount > 900000 Then
        MsgBox (“무한루프 상태입니다. 함수를 중지하고 빠져나갑니다.”)
        Exit Do
    End If
   
    ‘다음 로우를 체크
    tempRow = tempRow + 1
Loop

 

‘beginCellAddr 셀부터 endCellAddr 셀까지 영역 선택한다.
MsgBox (“[” & beginCellAddr & “:” & endCellAddr & “] 영역 선택한다.”)
Range(beginCellAddr, endCellAddr).Select

 

End Function

[엑셀] 이동하거나 복사하려는 시트에 … 이 이름을 사용하시겠습니까? 메시지 해결

[엑셀] 이동하거나 복사하려는 시트에 … 이 이름을 사용하시겠습니까? 메시지 해결

엑셀에서 워크시트를 이동하거나 복사할 경우 아래와 같은 메시지가 나오는 경우가 있다.

이동하거나 복사하려는 시트에 대상 워크시트에 있는 이름 ‘OOOO’이(가) 있습니다. 이 이름을 사용하시겠습니까?
– 대상 시트에서 정의된 대로 이름을 사용하려면 [예]을 클릭하십시오.
– 수식이나 워트시트에서 참조하는 범위의 이름을 바꾸려면 [아니요]를 클릭한 후 [이름 충돌] 대화 상자에 새 이름을 입력하십시오.

문제는 이 메시지가 무수히 많이 나타나는 경우다.

해당 메시지가 몇 번 뜨지 않는다면 [예]/[아니오] 버튼을 적절히 선택하겠으나, 끝없이 뜬다면 워크시트 이동 또는 복사를 수행하기 어렵다.

<해결책>

1. 엑셀 상단 메뉴의 [수식] – [이름 관리자] 를 클릭한다.

 


2. 아래와 같이 [이름 관리자] 창이 나타날 것이다.

 

3. Shift 키를 누른채 목록의 여러 행을 선택하고, [삭제] 버튼을 클릭한다.

[선택한 이름을 삭제하시겠습니까?] 라는 메시지가 뜨면 [확인] 을 클릭한다.

 

4. 이름 목록이 잘 제거되었다.


이제 워크시트를 이동 또는 복사할 경우 [이동하거나 복사하려는 시트에 … 이 이름을 사용하시겠습니까?] 메시지가 뜨지 않을 것이다.