Nov 7, 2012 - 자바에서 외부 프로그램/외부 명령어를 실행

ProcessBuilder : 자바에서 외부 프로그램/외부 명령어를 실행할때 사용하는 Class 입니다.

package com.sec.infolink.admin.common.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * Class Name : CommandExecuteUtil.java
 * Description : 
 *
 * Modification Information
 * Mod Date		Modifier	Description
 * ----------	------		---------------------------
 * 2012.11.07	젊은광대		최초생성
 *
 * 

 *
 * @author 젊은광대
 * @since 2012.11.07
 * @version 1.0
 */
public class CommandExecuteUtil {

	protected final static Log log = LogFactory.getLog(CommandExecuteUtil.class);
	
	public static String executeArr(String[] command, Log log) throws IOException {

		ProcessBuilder b = new ProcessBuilder(command);
		StringBuffer ret = new StringBuffer();

		  //표준 에러 출력을 머지 해 출력한다
		  b.redirectErrorStream(false);
		  Process p;
		
		  p = b.start();
		
		  
		  BufferedReader reader = new BufferedReader( new InputStreamReader( p.getInputStream() ) );
		  String line = null;
		  
		  //표준 에러 출력이 표준 출력에 머지 해 출력되므로, 표준 출력만 읽어내면 된다
		  while ((line = reader.readLine()) != null) {
			  ret.append(line);
		  }
	      
	      return ret.toString();
	}

	
	public static String execute(String command, String arg1, String arg2) throws IOException {

		// sh 파일이나 기타 FullPathTarget 으로 파일을 실행할 수 있다.
		// 실행파일 이름만으로 실행이 가능하다.
		// 다음과 같은 경우 (실행파일, 옵션, 어규먼트) 형식으로 인식한다.
		// 리눅스를 예를 들자면 rm -rf *.sh 형식이라고 이해하면 된다.
		// ProcessBuilder builder = new ProcessBuilder("rm -rf *.sh");
		// 으로 할 경우 인식을 제대로 못하기 때문에 분리해서 각각 입력한다.
		// ProcessBuilder builder = new ProcessBuilder("rm","-rf","*.sh");
		ProcessBuilder builder = new ProcessBuilder(command, arg1, arg2);
		StringBuffer ret = new StringBuffer();
		Process process = null;
		
		try {
			// 표준 에러 출력을 머지 해 출력한다
			// builder.redirectErrorStream(false);
			process = builder.start();
			
			BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) );
			
			// process.waitFor();
			// 실행한 프로세스가 종료될 때까지 대기한다. 
			// 만약 실행한 프로세스가 동기식으로 처리되어야 한다면 사용해야 한다.
			// 하지만 그렇지 않다면 사용해서는 안된다. 
			// 예를 들어 데몬이나 서비스 형태의 프로세스를 실행하고자 하는 상황에서 
			// 이 명령을 사용하면 해당 프로세스가 종료될 때까지 이 명령을 수행한 프로세스는 진행되지 않는다.
			int i = process.waitFor();
			
			if (0 != i){
				log.info("Command Execute Fail : << " + i + " >>");
				log.debug("builder command : " + builder.command().toString());
			} else {
				log.debug("builder command : " + builder.command().toString());	
			}
			
			log.info(process.exitValue());

			String line = "";
			// 표준 에러 출력이 표준 출력에 머지 해 출력되므로, 표준 출력만 읽어내면 된다
			while ((line = reader.readLine()) != null) {
				ret.append(line);
			}
			
			// process.destroy();
		} catch (IOException e) {
			log.info(command + " " + "arg1 : " + arg1 + "arg2 : " + arg2);
			throw new RuntimeException(e);
		} catch (InterruptedException e) {
			log.info(command + " " + "" + "");
			throw new RuntimeException(e);
		} finally {
			if ( null != process) {
				closeQuietly(process.getInputStream());
				closeQuietly(process.getOutputStream());
				closeQuietly(process.getErrorStream());
				// 해당 프로세스를 종료시킨다. 
				// 역시 데몬이나 서비스 형태의 프로세스 호출시에는 사용해서는 안된다. 
				// 호출하면 해당 프로세스는 종료된다. 
				process.destroy();
			} else {
				log.info(command + " " + "arg1 : " + arg1 + "arg2 : " + arg2);
				log.info("Cannot close Process Streams");
				throw new RuntimeException("Cannot close Process Streams");
			}
		}
		return ret.toString();
	}
	
	public static void closeQuietly(Writer output) {
		 try {
			 if (output != null) {
				 output.close();
			 }
		 } catch (IOException ioe) {
			 //ignore
		 }
	 }
	 
	 public static void closeQuietly(Reader output) {
		 try {
			 if (output != null) {
				 output.close();
			 }
		 } catch (IOException ioe) {
			 //ignore
		 }
	 }
	 
	 public static void closeQuietly(InputStream output) {
		 try {
			 if (output != null) {
				 output.close();
			 }
		 } catch (IOException ioe) {
			 //ignore
		 }
	 }
	 
	 public static void closeQuietly(OutputStream output) {
		 try {
			 if (output != null) {
				 output.close();
			 }
		 } catch (IOException ioe) {
			 //ignore
		 }
	 }
	 
	 /*
	  * 실행 예제 : 리눅스와, 윈도우 환경에 따라서 다르게 동작하도록 작업해둡니다.
	  */
	 public static void main(String[] args) {
		  // OS 구분하여 명령어 처리 - 로컬(window) Dos 작업환경을 위해
         String osType = "unix";
         if(System.getProperty("os.name").toLowerCase().indexOf("win") >= 0 ){
         	osType = "window";
         }
         log.debug("OS check Test : "+System.getProperty("os.name").toLowerCase());
         String retail_size = "";
         // 윈도우와 리눅스 둘다 동작하도록 작업.
         try {
	         if(osType.equals("window")){
	        	 String cmd[] = new String[3];
	        	 cmd[0] = "cmd.exe";
	        	 cmd[1] = "/C";
	        	 cmd[2] = "dir";
	        	 retail_size = CommandExecuteUtil.executeArr(cmd, log);
	         }else{
	        	 retail_size = 
	        		 CommandExecuteUtil.execute(
	        				 "rm", 
	        				 "-rf", 
	        				 "*.*");
	         }
         } catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
         }
         log.info(retail_size);
	}
}

티스토리에서 옮기면서 추가적으로 적자면, 네이버에서는 2가지 방법에 대한 가이드가 있어 참조합니다.


참조


Aug 1, 2012 - IP를 수치로 변환, 수치를 IP로 변환하는 법.

  1. 입력된 IP 값을 계산 가능한 수치로 변환하여 반환하는 로직입니다.

※ 정해진 규칙

 public static Long ipToInt(String ipAddr) {
        String[] ipAddrArray = ipAddr.split("\\.");

        long num = 0;
        for (int i=0;i<ipAddrArray.length;i++) {
            int power = 3-i;
            /*
             * i의 값으 범위는 0~255 사이이며, 그 값을 256으로 나눈 나머지 값에  
             * 256을 power의 값인
             * 1~9 사이는 2,
             * 10~99 사이는 1,
             * 100 이상은 0 의 값에 누승하여 num 값에 더함
             */
            num += ((Integer.parseInt(ipAddrArray[i])%256 * Math.pow(256,power)));
        }
        return num;
    }

  1. Long 값을 IP 로 변환하는 로직입니다. 기본이 Int 형이나, int 형의 범위가 -2147483648 ~ 2147483647 입니다. 그런데 IP 를 수치로 변환하게 되면 2147483647 를 넘는 경우가 생깁니다. 그래서 Long 형으로 처리해야합니다. ^^

※ 정해진 규칙

 public static String intToIp(long num) {
        return ((num >> 24 ) & 0xFF) + "." +
               ((num >> 16 ) & 0xFF) + "." +
               ((num >>  8 ) & 0xFF) + "." +
               ( num        & 0xFF);
    }

결국 이것은 약속이므로 임의대로 바꾸면 안됩니다.

부연설명을 드리자면,

※ DB Call 1. Maxmind 에서 나오는 DB Query

SELECT MOD(ROUND(1044455424 / 16777216), 256) AS X
,MOD(ROUND(1044455424 / 65536), 256) AS Y
,MOD(ROUND(1044455424 / 256), 256) AS A
,MOD(1044455424, 256) AS Z FROM DUAL;
Maxmind(http://www.maxmind.com/) 사이트에서 찾을  있는 DB Query 지만, 실은 이것과 위의 자바 상의 로직은 결과값이 틀립니다.

예를 들자면, 1357611007 로 확인했을때, 자바소스는 80.235.127.255 , DB Query 는 81 235 128 255 가 나옵니다.

※ DB Call 2.

SELECT MOD(TRUNC(1357611007 / 16777216), 256)
,MOD(TRUNC(1357611007 / 65536), 256)
,MOD(TRUNC(1357611007 / 256), 256)
,MOD(1357611007, 256) FROM DUAL;

만약 DB Call 로 검색할려고 한다면, 해당 방식으로 진행해야합니다. 비트 단위로 쉬프트 한 다음에 8자리 비트만 확인하여 각 자리 수를 확인하는 로직이므로 DB 도 마찬가지로 진행해야합니다.

May 31, 2012 - Ext Js 에서 text 는 중앙정렬, 컬럼내용은 좌우정렬하는 방법

text : '<span style="padding: 0px 0px 0px 60px">Country Name</span>',
width : 200, 
align : 'left',
sortable : true,
dataIndex: 'SHORT_COUNTRY_NM'

기존에는 이런식으로 중앙 좌측 정렬인데 중앙 정렬을 하기 위해서는 text 안에 옵션을 주었다. 이게 엄청나게 짜증이 난다. 1px 단위로 수정하고나서 실제로 변경되었는지 확인해야하기 때문이다.

그런데 이 방법이 말고, text는 중앙정렬이 되면서, 컬럼안에 들어있는 값은 좌측, 우측으로 정렬이 가능한 방법이 있었다.

바로

style: 'text-align:center'

을 주는 방법이었다.

text : 'Country Name',
style : 'text-align:center',
width : 200, 
align : 'left',
sortable : true,
dataIndex: 'SHORT_COUNTRY_NM'

그렇게 되면 정상적으로 “Country Name”이라는 헤더값이 중앙정렬이 되면서, 출력되는 컬럼값들은 전부 좌측정렬이 된다.