2015.03.22 13:17

▶ Apache Tika 예제 테스트

  • 다양한 문서에서 메타 정보와 컨텐츠를 추출하기 위해 사용할 수 있는 유용한 라이브러리는 아파치 Tika이다.
  • Docar 프로젝트에 Tika를 적용을 위한 사전 검증으로서 프로젝트 환경 설정 및 예제 테스트를 수행한다.

▶ 프로젝트 환경 설정 및 예제 작성

  • Maven 환경 설정
    : 프로젝트 내에서 Tika를 사용하기 위해 의존성(dependencies)을 설정한다. 2015년 3월 기준, 아파치 Tika의 최신 버전은 1.7 이다.

     	<!-- [end] 아파치 Tika 라이브러리 -->
      <properties>
     	<tika.version>1.7</tika.version>
      </properties>
    
            <!-- [start] 아파치 Tika 라이브러리 -->  	
     	<dependency>
     		<groupId>org.apache.tika</groupId>
     		<artifactId>tika-core</artifactId>
     		<version>${tika.version}</version>
     	</dependency>
     	<dependency>
     		<groupId>org.apache.tika</groupId>
     		<artifactId>tika-parsers</artifactId>
     		<version>${tika.version}</version>
     	</dependency>
    


  • 예제 코드 작성
    : MS Word 문서 파일 읽어들여, 메타 데이터를 검출하고 컨텐츠를 추출하는 예제는 다음과 같다.

    package docar.archive.extraction;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    import org.apache.tika.exception.TikaException;
    import org.apache.tika.metadata.Metadata;
    import org.apache.tika.parser.AutoDetectParser;
    import org.apache.tika.parser.ParseContext;
    import org.apache.tika.parser.Parser;
    import org.apache.tika.sax.BodyContentHandler;
    import org.xml.sax.SAXException;
    
    public class AutoDetectionExample {
    	public static void main(final String[] args) throws IOException,
    			SAXException, TikaException {
    		Parser parser = new AutoDetectParser();
    
    		System.out.println("------------ Parsing an Office Document:");
    		extractFromFile(parser, "/demo.doc");
    	}
    
    	private static void extractFromFile(final Parser parser,
    			final String fileName) throws IOException, SAXException,
    			TikaException {
    		long start = System.currentTimeMillis();
    		BodyContentHandler handler = new BodyContentHandler(10000000);
    		Metadata metadata = new Metadata();
    		InputStream content = AutoDetectionExample.class
    				.getResourceAsStream(fileName);
    		parser.parse(content, handler, metadata, new ParseContext());
    		for (String name : metadata.names()) {
    			System.out.println(name + ":\t" + metadata.get(name));
    		}
    		System.out.println(String.format(
    				"------------ Processing took %s millis\n\n",
    				System.currentTimeMillis() - start));
    		System.out.println("------------ content of document\n" + handler.toString());
    	}
    }
    


▶ 추가 고려 사항

  • 다양한 문서 형식에서 추출할 수 있는 메타 데이터에서 '문서관리 시스템'에서 활용할 수 있는 유용한 후보(candidates)들은 다음과 같다. : "Content-Type", "Author", "Last-Modified", "Creation-Date", "title"
  • "Content-Type" : 파일 형식 별 검색을 위해 필요한 정보이므로 필수 항목이다.
  • "Author" : "작성자" 혹은 "저자" 정보는 MS-Office 등의 문서 형식에서 부정확한 경우가 많다. 예를 들어 타인이 작성한 문서를 복사 및 편집한 후 저장한 문서일 경우에는 원래 파일의 작성자 정보만이 남아 있게 된다. 따라서, 부수적인 정보로 취급해야 한다.
  • "Creation-Date" : 문서의 생성일자는 특정 기간에 생성된 문서를 검색하는데 유용한 정보이다.
  • "Last-Modified" : 문서의 최종 변경일자는 버전 관리를 위해 필요한 정보이다. 문서 저장소에 저장된 파일과 입력된 파일을 비교해서 입력된 파일의 변경일자가 저장소의 파일보다 나중인 경우에는 새로운 버전으로 문서 저장소에 추가해야 한다.
  • "title" : 제목 또한 부정확한 정보가 될 수 있다. 앞서 작성자 정보와 마찬가지로 복사한 파일을 편집한 후에 저장하면 이전 작성자의 정보가 그대로 남아있게 된다.

▶ 참조 링크 및 이클립스 프로젝트 (소스 다운로드)


Posted by 善 곽중선
2015.03.21 13:21

▶ Non Functional Requirement

  • "성능 측정(performance estimation) 혹은 프로파일링(profiling)"은 서비스 혹은 어플리케이션이 '실용적인지 여부'를 판단하는 기준이 된다. 아무로 기능 면에서 좋은 소프트웨어라고 할지라도 필요한 시스템 사양(specification)이 과다하다면 써먹을 수 없다. 혹은 실행 속도가 느리다면 환영 받지 못할 것이다. 
  • 성능 측정을 모든 기능을 개발한 후에 수행하게 되면 2가지 문제가 발생할 수 있다.

    • 성능 문제를 해결하는데 필요한 시간이 부족해서, 최초에 수립한 일정을 지키기 못하거나,
    • 문제를 해결할 수 없어서 실패한 프로젝트가 되고 마는 것이다. 

  • 따라서, 성능 측정 준비는 기능 설계를 진행하면서 함께 준비하고, 기능 구현 과정 중에 몇 차례에 걸쳐 성능을 측정함으로써 중간 과정에서 취약점(weak point)가 어느 부분인지, 어떤 기능으로 인해 급작스럽게 성능이 하락하는지 검사하는 것이 현명하다.


▶ 성능에 대한 핵심 개념과 사전 지식

  • "성능(performance)"를 좌우하는 2가지 요소는 "공간"과 "시간"이다. 프로그래밍에서 "공간"이라는 개념은 프로그램이 소모하는 메모리(작게는 메인 메모리, 크게 보면 디스크와 네트워크)이며, "시간" 개념은 얼마나 CPU 자원을 오래 사용하는가 이다.
  • 따라서, 소프트웨어 성능은 "메모리 사용량(공간")과 "CPU 사용량(시간)" 등 2가지 측정 지표(metrics)가 존재한다.
  • 개발자가 작성하는 "코드"가 얼마나 많은 "공간"과 "시간"을 필요로 하는가에 대해서는 "알고리즘"과 "자료구조" 지식을 통해 이론적으로 예측할 수는 있으나, 다음과 같은 조건에 따라 "같은 문제"라고 하더라도 실질적으로 상당한 차이가 발생한다.

    • 어떤 언어로 개발하는가?
      : 하위 수준 언어일수록 빠르고, 컴파일러(compiler) 방식의 언어가 인터프리터(interpreter) 언어보다 빠르다.
    • 개발자가 구체적으로 어떤 방법으로 구현했는가?
      : 구현 방식은 API 호출, 라이브러리 활용, 직접 구현 등 방식을 한정할 수 없다.
    • 어떤 컴파일러를 사용해서 컴파일 했는다?
      : 컴파일러에 따라서 기계어를 생성하는 로직이 다르다. 컴파일러 자체에 성능 최적화 기능이 포함되어 있는 경우도 많다.
    • 어떤 머신(machine)에서 실행하는가?
      : 게임과 같은 실수형 계산 및 그래픽 처리를 많이 하는 프로그램은 GPU 성능에 의존하는 경향이 있다.

  • 위와 같은 이유로 성능은 정적인 "코드"만으로 예측할 수 없기 때문에 실행하면서 측정하는 것이 가장 '정확하다'! 프로그램을 실행하면서 성능을 측정하고, 분석하는 행위를 "프로파일링(profiling)"이라고 하며, 프로그램의 성능을 측정하는 도구를 "프로파일러(profiler)"라고 한다. 프로그램의 성능을 측정한 후, 개선하는 작업을 "최적화(optimizing)"라고 한다.

▶ 성능에 대한 정의와 이해 at slideshare.net



▶ DIY Profiling at slideshare.net



▶ 성능 측정 유형

  • CPU 측정
    • Times/ Calls 
      : 함수 혹은 메소드가 얼마나 오랜동안 CPU를 사용하는가 혹은 얼마나 많이 호출되는가를 측정하는 것이다.
    • Sampling
      : 아주 짧은 시간동안 호출되는 함수의 수행 시간을 측정하면 해당 함수의 수행 시간을 왜곡하게 된다. (함수가 수행되는 시간보다 더 많은 시간을 측정하는데 소모하거나, 수행시간이 느린 것으로 잘못 판단할 수 있다.) 혹은 초당 10만번 이상 호출되는 함수들을 측정하고자 할 경우 샘플링(sampling) 기법을 사용한다. 샘플링 기법은 프로그램 수행 중 일정 주기 마다 현재 실행 상태 (실행 중인 위치 정보)를 기록하는 기법이다.

  • 메모리 측정
    • Usage
      : 메모리를 얼마나 "자주" 할당 및 해제하는가를 측정한다. 메모리의 할당 및 해제는 비용이 많이 드는 '비싼' 작업이다.
    • Allocation
      : 메모리를 얼마나 "많이" 할당하느냐를 측정한다. 메모리를 과다하게 할당할 경우 "메모리 부족(Out of memory)" 오류를 발생 시키거나, 스왑(swap) 메모리를 사용하게 되어 급격한 성능 저하를 유발하게 된다.


▶ 스스로 하는 성능 측정 (DIY Java Profiling)

  • 앞서 Romon Elizrov 의 자료에서 제시하는 프로파일링 기법들은 다음과 같다.

  • 자바 코드 내에서 성능 측정 코드 삽입 (Just code it in Java)
    : 개발자가 직접 성능을 측정하는 코드를 자바 코드 내에 삽입하는 방법이다.

  • 자바 가상 머신의 기능 활용 (Know your JVM features)
    : JVM 에서는 메모리 사용 상태 및 스레드(thread)의 실행 상태를 확인할 수 있는 기능(옵션)이 제공된다.

  • 바이트 코드 조작 활용 (Use byte code manipulation)
    : 자신이 작성한 프로그램이 아니거가 - 소스 코드가 없다거나 -, 이미 완성된 프로그램의 성능을 측정하고자 하는 경우에 사용되는 기법이다. 컴파일된 클래스 파일 (확장자가 .class)을 조작해서 성능 로그를 생성하는 코드를 삽입하는 기법이며, 자칫 JVM의 비정상 종료(crush)를 유발할 수 있기 때문에 조심스럽게 적용해야 한다.

▶ 프로파일링 클래스 구현

  • docar.util.profile.Profiler
    : 성능 측정 정보를 기록하는 싱글턴(singleton) 패턴의 클래스. addCallEvent() 메소드를 호출할 때마다, 특정 기능의 수행 횟수 및 누적 수행 시간을 기록한다.
  • docar.util.profile.StopWatch
    : 특정 기능(메소드, 클래스)의 수행 시간을 측정하는 클래스.
  • docar.uti.Report
    : 성능 측정 결과를 출력하는 클래스. "기능 명칭 및 호출 횟수", "총 수행 시간", "평균 수행 시간" 등을 출력한다.

  • 사용 방법 예시
    // 메소드 시작 부분에서 "기능 명칭"을 인자로 스톱워치 생성
    StopWatch stopWatch = new StopWatch("DuplicationFinder.main");
    
    // 메소드 종료 부분에서 수행 시간 기록(누적)
    Profiler.getInstance().addCallEvent(stopWatch.stop());
    
    // 수행 시간 측정 결과 출력
    Report.report(System.out);
    
    
  • 측정 결과 예시



Posted by 善 곽중선
2015.03.20 03:15

▶ 로깅(logging) 설정 이전에 "이클립스 Maven 환경 설정"을 선행해야 한다.

▶ 로깅(logging 설정

  • pom.xml 파일에 slf4j 와 logback 의존성(dependency)를 추가한다.

    <dependencies>
  	<dependency>
  		<groupId>org.slf4j</groupId>
  		<artifactId>slf4j-api</artifactId>
  		<version>1.7.10</version>
  		<scope>runtime</scope>
  	</dependency>
  	<dependency>
  		<groupId>ch.qos.logback</groupId>
  		<artifactId>logback-classic</artifactId>
  		<version>1.1.2</version>
  		<exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
            </exclusion>
        </exclusions>
  	</dependency>
  	<dependency>
	    <groupId>org.slf4j</groupId>
	    <artifactId>jcl-over-slf4j</artifactId>
	    <version>1.7.7</version>
	</dependency>
  </dependencies>
  • logback 설정파일(logback.xml)을 생성한다.
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <logger name="docar" level="DEBUG"></logger>
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
  • log 출력 테스트 코드를 작성한다. 

▶ Maven 설정 및 로그 설정 프로젝트 백업

DocAr_MavenAndLogging.zip


▶ 참고 링크 : SLF4J 로깅 처리



Posted by 善 곽중선
2015.03.20 02:54
▶ 메이븐 기본 이해

메이븐(Maven)에 대한 기본 개념을 이해하기 위해서는 다음 슬라이드를 참조하기 바란다.


▶ 이클립스 메이븐 환경 설정


만일 이클립스에 메이븐이 설치되어 있지 않다면 다음 절차들을 따라하기 바란다.


  • https://www.eclipse.org/m2e/m2e-downloads.html 링크를 웹 브라우저에서 연다. 테이블 형태로 출력 m2e 플러그인 목록을 찾을 수 있을 것이다.
  • 가장 최신 버전을 선택하고, "p2 url" 컬럼의 URL 경로를 클립보드에 복사해둔다.

  • 이클립스로 돌아간 후, Help 메뉴를 선택하고 "Install New Software" 항목을 클릭한다.



  • "Add" 버튼을 클릭한 후, 아무 명칭이나 입력한다. "Location" 항목에 클립보드에 복사해둔 URL을 붙여넣는다.

  • 이전 단계에서 선택한 플러그인이 목록에 출력될  것이며, 앞에 표시된 체크박스를 체크한 후, "Next" 버튼을 클릭한다.

  • 설치가 진행되고, 설치가 완료되면 이클립스를 재시작한다.

  • 프로젝트를 우클릭하고, 설정(configure) 옵션 중에서 "convert to maven project"를 선택한다.

  • 새로운 POM을 생성하기 위한 "new wizard"가 나타날 것이다. 적절한 그룹 ID와 버전을 입력한다. "Packing as jar"를 선택한 후, "Finish" 버튼을 클릭한다.

  • Maven 프로젝트가 성공적으로 설치되었다. 이제 프로젝트가 성공적으로 Maven 프로젝트로 변환된 것이며, pom.xml 파일을 수정해야 한다.


Posted by 善 곽중선
2015.03.19 18:19

Apache Tika 개요

  • 아파치 Tika는 문서 타입 검출 및 다양한 파일 형식에서 컨텐츠를 추출하는 기능을 제공하는 라이브러리이다.
  • 내부적으로, Tika는 현존하는 다양한 문서 파서(parse)들과 문서 타입 검출 및 데이터를 추출하는 기법들을 활용하고 있다.
  • Tika를 활용해 광범위한(universal) 타입 검출기 및 스프레드 시트, 텍스트 문서, 이미지, PDF 및 멀티미디어 입력 포맷 등의 다양한 형식의 문서에서 구조화된 텍스트와 메타 데이터를 추출할 수 있는 컨텐츠 추출기를 제작할 수 있다.
  • Tika는 다양한 파일 형식을 파싱(parsing)할 수 있는 단일 범용 API를 제공한다. 각각의 문서 타입을 처리하기 위해 83가지 전문화된 파서(parser) 라이브러리를 사용한다.
  • 이러한 파서 라이브리들은 Parser 인터페이스라는 단일 인터페이스 내에 캡슐화 되어 있다.


왜 Tika를 써야 하는가?

filext.com 에 따르면 1,5000 에서 51000 가지의 컨텐츠 타입이 존재하는 것으로 추정되며, 이러한 수치는 날마다 증가하고 있다. 데이터 텍스트 문서, 엑셀 스프레드 시트, PDF, 이미지, 멀티미디어 등 다양한 형식으로 저장 되어지고 있다. 따라서, 검색엔진과 컨텐츠 관리 시스템 등의 어플리케이션은 다양한 문서 타입에서 손쉽게 데이터를 추출할 수 있는 기능을 필요로 한다. Apache Tika는 다양한 파일 형식에서 데이터를 찾아내고 추출하는 범용적인 API를 제공하기 위해 만들어 졌다.


Apache Tika 어플리케이션들

다양한 어플리케이션들을 Tika를 이용해 만들 수 있다.


검색 엔진(Search Engine)

Tika는 검색 엔진이 디지털 문서들의 텍스트 컨텐츠를 색인하는데 사용된다.


문서 분석(Document Analysis)

인공 지능 분야에서는 문서를 자동으로 의미론(semantic)적으로 분석하고, 모든 유형의 데이터를 추출하는 도구들이 있다.

이러한 어플리케이션에서는 문서에서 추출된 컨텐츠에 포함된 중요한 용어들을 기반으로 문서들이 저절로 분류된다.

이러한 도구들에서 Tica는 문서를 분석하기 위해 컨텐츠를 추출하는 기능을 수행한다.


디지털 자산 관리 (Digital Asset Management)

어떤 기관들은 사진, e-book, 음악, 설계도, 그림, 비디오 등의 디지털 자산을 관리하는 특별한 어플리케이션을 사용하는데 이를 디지털 자산 관리 시스템이라고 부른다.

이러한 어플리케이션에서 문서의 타입 검출 기능과 다양한 문서들을 분류하기 위한 메타 데이터 추출기를 사용한다.


컨텐츠 분석(Contents Analysis)

Amazon 같은 웹 사이트들은 고객들의 관심사에 기반한 맞춤형 컨텐츠를 제공한다. 이렇게 하기 위해, 머신 러닝(machine learning) 기술을 적용하거나, Facebook 같은 소셜 미디어 네트워크의 도움을 받아 사용자의 기호(interest)와 좋아하는 것들에 대한 정보를 수집한다. 이렇게 수집된 정보들은 HTML와 그외의 다양한 형식으로 존재하며, 컨텐츠 타입 검출 및 추출 과정을 거치게 된다.

문서의 컨텐츠를 분석하기 위해 UIMA 및 Mahout 같은 머신 러닝 기술이 활용된다. 이러한 기술들은 문서의 데이터를 클러스터링하고 분석하는데 쓰인다.

Apache Mahout는 Apache Hadoop 위에서 Machine Learning 알고리즘을 제공하는 프레임워크이다. Mahout은 클러스터링 및 필터링 기술을 제공하며, 프로그래머들은 자신만의 고유한 ML 알고리즘을 이용해 다양한 텍스트와 메타데이터 조합을 이용해 추천 정보를 생산할 수 있게 된다. 최근 버전의 Mahout은 ML을 위한 입력 데이터를 생성하기 위해 Tika를 이용하고 있다.

Apache UIMA 는 다양한 프로그래밍 언어를 분석하고 처리하고, UIMA 어노테이션(annotations)을 생성한다. 내부적으로 Tika Annotator를 문서의 텍스트와 메타 데이터를 추출하기 위해 사용한다.


출처 : http://www.tutorialspoint.com/tika/tika_quick_guide.htm

Posted by 善 곽중선
2015.03.16 16:00

문서(혹은 파일)을 저장하는 메커니즘을 설계하기 위해 "문서관리시스템 (EDMS : Electronic Document Management System)" 관련 기술을 구글에 검색하니, 스택오버플로우(stackoverflow) 사이트에 아래와 같은 질의 응답이 올라와 있는 것을 발견했다. 한 때, EDMS 와 그룹웨어(groupware) 제품을 개발했었는데, 한동안 다른 일을 하다보니 최신 표준과 기술 변화를 감지하지 못한 것을 반성한다. 돌다리도 다시 두들겨 보라는 옛말을 이제는 아는 것도 구글링 해봐야 한다는 말로 바꾸어야 할 듯 싶다.


스택오버플로우 질의응답 : 단순한 문서 관리 시스템 구현 질의응답


다음은 영문 질의 응답 내용을 요약한 것이다.


[질문]

다음과 같은 요구사항을 만족하는 단순한 문서관리 시스템을 구현하려면 어떻게 해야 하는가?

(단순함의 의미가 기능이 적다는 의미일 수도 있고, 쉽게 구현할 수 있다는 의미가 될 수도 있음)


  1. 분산 웹 어플리케이션
  2. 문서 버전 관리 지원
  3. 문서 잠금(locking) 기능 지원
  4. 문서 검색

[답변]

바퀴를 다시 만들지 말고 표준 기술인 JCR을 활용하세요. JCR은 잘 정의된 표준 (많은 사람들의 시간과 기술을 투자한)입니다. JCR의 구현체(implementation)인 jackrabbit 을 권장합니다.


[관련 표준]

JSR : 자바 스펙 요구서(Java Specification Request,JSR)은 자바 플랫폼에 추가된 사양 및 기술을 기술하는 공식 문서.

Posted by 善 곽중선
2015.03.14 23:38

오픈소스 문서관리 시스템의 첫 단계인 "파일 중복 검사 어플리케이션" 제작 과정에 대한 요약 및 안내 입니다.


문서(document) 정의와 구조

 : "문서"에 대한 용어 정의와 문서의 속성들에 대한 정리, 문서의 하위 객체와 문서의 관계 등을 정립 등.


파일(File) 클래스 설계 - 상태과 행위

  : "파일" 클래스의 역할, 행위와 속성들에 대한 설계


파일(File) 클래스 설계 - 버전 클래스 분리

  : "파일" 클래스에서 버전 클래스를 분리하게된 이유 설명


파일(File) 클래스 설계 - 인스턴스 생성

  : 인스턴스를 생성하는 2가지 기법에 대한 설명과 파일 인스턴스를 생성 방식을 결정하기 위한 기술 검토.


문서(document)의 색인 속성 설계 

  : 문서에 포함되는 속성들에 대한 자료형, 타입 등에 대한 검토


FileVersion 클래스 구현

  : FileVersion 클래스 소스 및 설명


File 클래스 구현 

  : File 클래스 소스 및 설명


Document 클래스 구현

  : Document 클래스 소스 및 설명


DocumentBuilder 클래스 구현

  : DocuemntBuilder 클래스 소스 및 설명


파일 중복 검사

  : 파일 중복 검사 어플리케이션 소스 및 설명


Posted by 善 곽중선
2015.03.14 22:59
  • 문서 관리 시스템의 로드맵(loadmap) 중에서 첫번째 어플리케이션은 파일 중복 검사 프로그램입니다.

  • 중복 방지 (preventing duplication) : 다양한 저장 매체(storage media)에서 문서를 수집할 때, 중복이 발생할 여지는 충분하다. 사용자가 다양한 환경에서 작업하면서 파일을 복사하는 경우가 빈번하기 때문이다. (USB 복사, 이메일 전송, SNS 공유 및 백업 등) 따라서, 다양한 매체에서  데이터를 수집할 때 이미 수집된 파일이 존재할 경우 새롭게 입력된 파일을 무시하는 기능은 필수적이다. 중복 방지 기능을 구현하기 위해서는 무결성 검사 혹은 check sum 기법을 적용해야 한다. 중복 방지 절차를 간단히 묘사하면 아래와 같다.

    • 파일을 저장소에 등록할 때 컨텐츠에 대한 체크섬을 생성한다.
    • 해당 체크섬에 해당하는 파일이 이미 존재하는지 검사한다. 만일 동일한 체크섬을 가진 파일이 존재한다면, checksum collision을 감안하여 동일한 파일 유무에 대한 정밀 확인 (파일 크기, 제목, 형식, 요약 등을 검사하고, 필요하다면 파일의 전반부 바이너리 데이터를 대조한다.)을 수행한다.
    • 동일한 파일이 존재하는 것으로 판단되면, 저장소에 등록하지 않는다.

  • 실행 결과 예시는 아래와 같습니다.


Posted by 善 곽중선
2015.03.14 22:52
  • Document 클래스의 객체를 생성하기 위해서는 File 및 FileVersion 객체를 함께 생성해야 합니다. Document 객체만 생성해서는 쓸모가 없기 때문입니다.
  • Document 클래스 내에 File 및 FileVersion 객체를 생성할 수 있으나, 소프트웨어 설계 시에 가급적 KISS (Keep It Simple Stupid!) 원칙을 기억해야 합니다. 한번에 많은 기능을 구현할 수도 있고, 때로는 그런 방식으로 작업하는 것이 생산성이 높다고 여겨질 수 있습니다. 그러나, 소프트웨어는 살아있는 생명이며, 최초에 모든 요구사항(기능명세)을 명확히 결정하고 코딩할 수 없습니다. 따라서, 각각의 클래스는 최소한의 기능을 담도록 하고 변경이 발생할 가능성이 줄어들게끔 적은 라인의 코드를 담는 것이 좋습니다. 작은 모듈은 더 이상 필요 없을 때 폐기하는데도 유리합니다.
  • 따라서, Document 클래스 자체는 문서를 표현하는 데이터를 담는 역할만 수행하도록 제한하고, 문서와 그에 따른 부가적인 객체를 생성하는 역할을 별도의 빌더(builder) 클래스로 분리했습니다.
  • 나아가, 빌더 자체도 Document, File, FileVersion 을 생성하는 역할로 제한했습니다. 문서를 분석하고, 키워드, 요약, 체크섬 등을 생성하는 작업은 ContentAnalyzer 클래스로 분리했습니다.

ContentAnalyzer.java


DocumentBuilder.java

public class DocumentBuilder {
...... 중략 .....
	public Document createDocument() {
		
		// ---- 필수 항목 점검 및 analyzer 준비 ----
		if(location == null) {
			throw new IllegalStateException("location property is not specified");
		}
		if(writer == null) {
			throw new IllegalStateException("writer property is not specified");
		}
		
		ContentAnalyzer contentAnalyzer = new ContentAnalyzer(location);
		contentAnalyzer.analyze();

		// ---- 입력 값이 없는 항목에 대한 대체 처리 -----
		if(createDate == null) {
			createDate = new Date(Calendar.getInstance().getTimeInMillis());
		}
		if(title == null) {
			title = contentAnalyzer.getFileName();
		}

		// -- FileVersion, File, Document 순서로 빌드 -----
		FileVersion fileVersion = new FileVersion(1, createDate, description);
		File file = new File(location, contentAnalyzer.getChecksum(), contentAnalyzer.getSize(), fileVersion);
		Document document = new Document(title, writer, keywords, 
                            contentAnalyzer.getAbstraction(), location, contentAnalyzer.getFormat());
		document.addFile(file);
		
		return document;
	}
}
package docar.archive.document;

/**
 * 문서 파일을 분석하는 클래스.
 * 
 * @author "Sunny Kwak"
 */
public class ContentAnalyzer {
	private static final String HTTP_PROTOCOL = "http";

	private URL location;
	private String fileName;
	private long fileSize;
	private String checksum;

	public ContentAnalyzer(URL location) {
		this.location = location;
	}

	public void analyze() {
		InputStream inputStream = null;

		try {
			String protocol = location.getProtocol();
			if (protocol == null
					|| !protocol.toLowerCase().equals(HTTP_PROTOCOL)) {
				inputStream = new FileInputStream(new java.io.File(location.toURI()));
			} else {
				inputStream = location.openConnection().getInputStream();
			}

			fileName = location.getFile();
			fileSize = 0L;
			checksum = getMD5Checksum(inputStream);

		} catch (IOException e) {
			// FIXME add logging
			e.printStackTrace();
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException ignored) {
					// do nothing
				}
			}
		}

	}


Posted by 善 곽중선
2015.03.14 22:35
  • Document 클래스의 속성은 제목 (title), 작성자 (wirter), 키워드 목록 (keywords), 요약(abstraction), 원본 링크 (original link), 파일 형식 (format), 버전 파일 목록 (files) 등입니다.
  • 작성자 정보는 이름, 이메일 주소, 홈페이지 등의 복합된 속성을 지녀야 하기 하기 때문에 별개의 클래스로 분리하였습니다. 또한, 한명의 작성자가 여러 문서를 작성하는 일대다(1:n) 관계이기 때문에 분리하는 것이 타당합니다.
  • 키워드는 복수의 명사 단어로 구성되며, 중복된 단어가 입력되어서는 안되기 때문에 Set<String> 타입을 선언합니다. 자료 구조 중에서 Set 타입은 포함된 데이터들의 중복을 허용하지 않는 특성을 가진다는 점을 활용하는 것입니다.
  • 파일 형식은 enum 타입을 사용했으나, 향후에 refactoring 가능성이 있습니다.
  • 하나의 문서가 여려 가지 버전으로 존재할 수 있고, 동일한 파일이 여러 위치에 존재할 수 있습니다. 버전을 고려했을 때 순서(order)가 중요하므로 List 타입을 사용했습니다.
package docar.archive.document;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import docar.user.Writer;

/**
 * '문서' 클래스.
 *
 * @author "Sunny Kwak"
 */
public class Document {
	private String title;
	private Writer writer;
	private Set<String> keywords;
	private String abstraction;
	private URL originalLink;
	private Format format;
	private List<File> files;

	public Document(String title, Writer writer, Set<String> keywords,
			String abstraction, URL originalLink, Format format) {
		if (title == null || title.isEmpty()) {
			throw new IllegalArgumentException("title argument is missing");
		}
		if (writer == null) {
			throw new IllegalArgumentException("writer argument is missing");
		}
		if (originalLink == null) {
			throw new IllegalArgumentException(
					"originalLink argument is missing");
		}
		if (format == null) {
			throw new IllegalArgumentException("format argument is missing");
		}

		this.title = title;
		this.writer = writer;
		this.keywords = keywords;
		this.abstraction = abstraction;
		this.originalLink = originalLink;
		this.format = format;

		files = new ArrayList<File>();
	}
Posted by 善 곽중선