2015. 3. 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. 3. 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 곽중선
2015. 3. 14. 22:19
  • File 클래스는 문서(Document)의 "물리적인 상태 정보"를 나타내는 클래스입니다. 파일 위치(URL), 체크섬(cheksum), 크기(size), 파일 버전(file version) 등의 속성을 포함하고 있습니다.
  • 파일 위치를 경로(path)가 아니라, URL 클래스로 선언한 것은 PC 혹은 서버의 로컬 디스크에 존재하는 파일 뿐만 아니라 인터넷 상에 존재하는 파일 위치를 지정할 수 있도록 하기 위해서입니다. checksum 문자열은 파일의 무결성(integrity)와 중복 검사(duplication check) 목적으로 사용됩니다.
  • File 클래스 또한 FileVersion 클래스 처럼 방어적 코딩이 포함되어 있습니다.



File.java

package docar.archive.document;

import java.net.URL;

/**
 * "파일"은 "문서"의 구성요소이다.
 * 
 * - 문서는 하나 이상의 파일을 포함할 수 있다.
 * - 각각의 파일은 문서의 다른 버전을 표현한다.
 * - 파일은 문서의 물리적인 형태를 의미한다.
 *
 * @author "Sunny Kwak"
 */
public class File {
	private URL location;
	private String checksum;
	private long size;
	private FileVersion fileVersion;

	/**
	 * '파일' 생성자.
	 * 
	 * @param location 파일의 위치
	 * @param checksum 파일 식별을 위한 지문(finger print)
	 * @param size 파일 크기.
	 * @param fileVersion 파일 버전.
	 * @throws IllegalArgumentException 필수 항목이 누락된 경우, 예외 처리
	 */
	public File(URL location, String checksum, long size,
			FileVersion fileVersion) {
		
		if(location == null) {
			throw new IllegalArgumentException("location arguement is missing");
		}
		if(checksum == null || checksum.isEmpty()) {
			throw new IllegalArgumentException("checksum argument is missing");
		}
		if(size < 1) {
			throw new IllegalArgumentException("Invalid fie size. Input file = " + location);
		}
		if(fileVersion == null) {
			throw new IllegalArgumentException("fileVersion argument is missing");
		}
		
		this.location = location;
		this.checksum = checksum;
		this.size = size;
		this.fileVersion = fileVersion;
	}

Posted by 곽중선