이번에는 ControllerSettings라는 마커 인터페이스를 도입하여 설계를 변경해보겠습니다. 이번에도 저는 TDD 프로가 아니라서;; 테스트 코드 부터 수정하진 못했습니다.. (흑흑.. 다음 부턴;; 테스트부터;???)

public interface CodeGenerationService {

    void generateController(ControllerSettings settings, Class domainClass) throws CodeGenerationException;

}

ControllerSettings 라는 인터페이스에는 아무것도 없습니다. 단순히 타입만 맞추가 위한 거죠.

그리고 FreemarkerControllerSettings 구현체를 만듭니다. 여기에 실제 프리마커 코드 생성기가 필요로 하는 인자값들이 들어갑니다. 이전 글에서 Map이 하던 역할을 이녀석이 하는거죠.

public class FreemarkerControllerSetting implements ControllerSettings {

    private String module;

    private String templateFileName;

    private String destinationDirPath;

    public FreemarkerControllerSetting(String module, String templateFileName, String destinationDirPath) {
        this.module = module;
        this.templateFileName = templateFileName;
        this.destinationDirPath = destinationDirPath;
    }

    public String getModule() {
        return module;
    }

    public String getTemplateFileName() {
        return templateFileName;
    }

    public String getDestinationDirPath() {
        return destinationDirPath;
    }
}

별도의 세터는 없어서 변경을 막고, 생성자를 이용해서 강제적으로 세 값 모두 받도록 했습니다. (이렇게 해도 널 체크는 하긴 해야겠지만... 아 이 귀찮은 널 체크... 하긴.. 어차피 뭔가가 null이면 어디선가 에러가 날테니 굳이 안해도 되겠네요. 캬캬캬)

그리고 이제 프리마커 코드 생성기 코드를 수정합니다.

public class FreemarkerCodeGenerationService implements CodeGenerationService {

    private Configuration configuration;
    private Stack<File> createdFilesWhileGenerateController;
    private Stack<File> createdFilesWhileGenerateDao;

    public FreemarkerCodeGenerationService(Configuration configuration){
        this.configuration = configuration;
    }

    public void generateController(ControllerSettings settings, Class domainClass) throws CodeGenerationException {
        FreemarkerControllerSetting fcSettings = (FreemarkerControllerSetting)settings;
        String module = fcSettings.getModule();
        String templateFileName = fcSettings.getTemplateFileName();
        String destinationDirName = fcSettings.getDestinationDirPath();

        Map<String, String> map = new HashMap<String,  String>();
        String className = domainClass.getSimpleName();
        map.put("module", module);
        map.put("domainClass", className);
        map.put("domainName", ClassUtils.getShortNameAsProperty(domainClass));

        createdFilesWhileGenerateController = new Stack<File>();

        Template controllerTemplate = null;
        try {
            controllerTemplate = configuration.getTemplate(templateFileName);
        } catch (IOException e) {
            throw new CodeGenerationException("template file loading fail with [" + templateFileName + "]", e);
        }

        File desticationFolder = new File(destinationDirName);
        boolean created = desticationFolder.mkdir();
        if(created)
            createdFilesWhileGenerateController.push(desticationFolder);
       
        desticationFolder = new File(destinationDirName + "/" + module);
        created = desticationFolder.mkdir();
        if(created)
            createdFilesWhileGenerateController.push(desticationFolder);

        File destinationFile = new File(destinationDirName + "/" + module + "/" + className + "Controller.java");
        FileWriter writer = null;

        try {
            writer = new FileWriter(destinationFile);
            controllerTemplate.process(map, writer);
            writer.flush();
            writer.close();
            System.out.println(destinationFile.getAbsolutePath()  + " created");
             createdFilesWhileGenerateController.push(destinationFile);
        } catch (IOException e) {
            throw new CodeGenerationException("destincation file creation fail", e);
        } catch (TemplateException e) {
            throw new CodeGenerationException("template processing fail", e);
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

...

}

음.. 확실히 Map에서 꺼내올 때에 비해서 뭔가 편합니다. 대체 Map에 어떤 키 값으로 데이터들이 들어있는지 애매한데다, 자쳇해서 스펠링이라도 틀리면;; 아무래도 그냥 이 방법으로 가야겠습니다.

마지막으로 테스트를 해봅니다. 물론 약간 고쳐야 하죠.

public class FreemarkerCodeGenerationServiceTest {

    @Test
    public void generationTst() throws IOException {
        Configuration configuration = new Configuration();
        configuration.setObjectWrapper(new DefaultObjectWrapper());
        configuration.setDirectoryForTemplateLoading(new FileSystemResource("doc/template").getFile());

        assertNotNull(configuration);

        FreemarkerCodeGenerationService service = new FreemarkerCodeGenerationService(configuration);

        service.generateController(new FreemarkerControllerSetting("test", "controller.ftl", "test/springsprout/modules"), Study.class);

        assertTrue(new File("test/springsprout/modules/test/StudyController.java").exists());
        service.deleteController();
        assertFalse(new File("test/springsprout/modules/test/StudyController.java").exists());
    }
}

잘 돌아갑니다. 그다지 여러 경우를 테스트하고 있지는 않지만 나름대로 최소한의 기능 보장은 해주기 때문에 안심하고 코딩을 계속할 수 있겠습니다.


현재 상태에서 커버리지를 색으로 표시해봤습니다. 몇가지 특수한 상황에 대한 테스트가 안 된 부분이 있는데 저 부분에 대한 테스트는 나중에 만들기로 하고 테스트 코드에 요약해 둡니다.

public class FreemarkerCodeGenerationServiceTest {

    @Test
    public void generationTst() throws IOException {
        Configuration configuration = new Configuration();
        configuration.setObjectWrapper(new DefaultObjectWrapper());
        configuration.setDirectoryForTemplateLoading(new FileSystemResource("doc/template").getFile());

        assertNotNull(configuration);

        FreemarkerCodeGenerationService service = new FreemarkerCodeGenerationService(configuration);

        service.generateController(new FreemarkerControllerSetting("test", "controller.ftl", "test/springsprout/modules"), Study.class);

        assertTrue(new File("test/springsprout/modules/test/StudyController.java").exists());
        service.deleteController();
        assertFalse(new File("test/springsprout/modules/test/StudyController.java").exists());
    }
   
    //TODO template file loading fail test
   
    //TODO destination file make fail test
   
    //TODO template processing fail test
   
    //TODO destination folder mkdir test
}

자 이제서야;; 본격적으로 Dao 코드 생성 작업에 들어갈 수 있겠군요.