Friday, December 12, 2014

Using Spring to Load Dictionaries from Local Resources

Introduction


Spring contains some useful design patterns for accessing local resources at runtime in a platform-independent manner.


Properties File


I debated initially whether the dictionary paths should be abstracted to a properties file. To do so without reason beyond "that's how it's always done" isn't sufficient. However, a maintenance justification may be found that any path changes will not require Spring bean changes, potentially eliminating the introduction of configuration errors.

Locations to the dictionary files are listed here:
 properties.adjectives.path          = dictionaries/adjectives.dat  
 properties.measurementWords.path    = dictionaries/measurement-words.dat  
 properties.nounPhrases.path         = dictionaries/noun-phrases.dat  
 properties.stopWords.path           = dictionaries/stop-words.dat  

I've added this properties file to this path:
commons-dict\src\main\resources\config\paths.properties
and the actual sources files (not listed here) exist at that location.


Dictionary Bean


 package org.swtk.common.dict.beans; 
 
 import java.util.Set;  
 import org.springframework.beans.factory.annotation.Autowired;  
 import org.springframework.beans.factory.annotation.Value;  
 import org.springframework.context.annotation.Lazy;  
 import org.springframework.core.io.Resource;  
 import org.springframework.stereotype.Component;  
 import org.swtk.common.dict.DictionaryBase;  
 import org.swtk.common.env.LogManager;  

 @Lazy  
 @Component("nounPhrasesDictionary")  
 public class NounPhrasesDictionary extends DictionaryBase {  

      public static LogManager     logger     = new LogManager(NounPhrasesDictionary.class);  

      @Autowired  
      @Value("${properties.nounPhrases.path}")  
      private Resource               resource;  

      @Override  
      public Set<String> entries() {  
           return set(resource);  
      }  
 }  

Path:
commons-dict\src\main\java\org\swtk\common\dict\beans\NounPhrasesDictionary.java



Dictionary Base


All dictionary beans extend this base class:
 package org.swtk.common.dict;  
   
 import java.io.BufferedReader;  
 import java.io.IOException;  
 import java.io.InputStreamReader;  
 import java.util.Set;  
 import java.util.TreeSet;  
   
 import org.springframework.core.io.Resource;  
 import org.swtk.common.env.LogManager;  
   
 public abstract class DictionaryBase implements Dictionary {  
   
      public static LogManager     logger     = new LogManager(DictionaryBase.class);  
   
      private String     beanName;  
   
      public String getBeanName() {  
           return beanName;  
      }  
   
      public Set<String> set(Resource resource) {  
           Set<String> set = new TreeSet<String>();  
   
           try {  
   
                BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));  
                for (String line = reader.readLine(); line != null; line = reader.readLine())  
                     set.add(line);  
   
           } catch (IOException e) {  
                logger.error(e, "Failed to Load Dictionary (name = %s)", getBeanName());  
           }  
   
           return set;  
      }  
   
      @Override  
      public void setBeanName(String beanName) {  
           this.beanName = beanName;  
      }  
 }  
   



application-config.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" xmlns:context="http://www.springframework.org/schema/context"  
      xmlns:jpa="http://www.springframework.org/schema/data/jpa"  
      xmlns:repository="http://www.springframework.org/schema/data/repository"  
      xmlns:tx="http://www.springframework.org/schema/tx"  
      xsi:schemaLocation="http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd  
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">  

      <context:component-scan base-package="org.swtk.common.dict.beans" />  
      <context:property-placeholder location="classpath:config/paths.properties" />  

 </beans>  

Path:
commons-dict\src\main\resources\config\application-context.xml



Container Access


If you have other projects in your workspace that are not Spring-enabled, you may find it useful to manage the application-context transparently.

I've encapsulated the context in a factory, and exposed each dictionary via an enum parameter:
 package org.swtk.common.dict;  

 import org.springframework.context.ApplicationContext;  
 import org.springframework.context.ConfigurableApplicationContext;  
 import org.springframework.context.support.ClassPathXmlApplicationContext;  
 import org.swtk.common.env.LogManager;  

 public class DictionaryContext {  

      private static DictionaryContext     instance;  

      public static LogManager               logger     = new LogManager(DictionaryContext.class);  

      public static DictionaryContext getInstance() {  
           if (null == instance) instance = new DictionaryContext();  
           return instance;  
      }  

      private ApplicationContext     context;  

      private DictionaryContext() {}  

      public void close() {  
           ((ConfigurableApplicationContext) getContext()).close();  
           setContext(null);  
      }  

      private ApplicationContext getContext() {  
           if (null == context) setContext(new ClassPathXmlApplicationContext("config/application-context.xml"));  
           return context;  
      }  

      public Dictionary getDictionary(DictionaryName dictionary) {  
           Dictionary dict = getContext().getBean(dictionary.toString(), Dictionary.class);  
           return dict;  
      }  

      private void setContext(ApplicationContext context) {  
           this.context = context;  
      }  
 }  



Dictionary Name Enum


Unfortunately, we are maintaining the beanId in two places now: this enum, and within the @Component annotation on each Spring Bean. This increases our maintenance burden slightly, but does make life easier for the consumer. Ultimately, I think this is worth it.

 package org.swtk.common.dict; 
 
 public enum DictionaryName {  

      ADJECTIVES("adjectivesDictionary"),  
      MEASUREMENT_WORDS("measurementWordsDictionary"),  
      NOUN_PHRASES("nounPhrasesDictionary"),  
      STOP_WORDS("stopWordsDictionary");  

      private String     beanId;  

      private DictionaryName(String beanId) {  
           setBeanId(beanId);  
      }  

      private String getBeanId() {  
           return beanId;  
      }  

      private void setBeanId(String beanId) {  
           this.beanId = beanId;  
      }  

      @Override  
      public String toString() {  
           return getBeanId();  
      }  
 }  



Test Client


The test client is simple, and the use of Spring transparent to the consumer:
 Dictionary dictionary = DictionaryContext.getInstance().getDictionary(DictionaryName.NOUN_PHRASES);  
 assertNotNull(dictionary.entries());  
 assertFalse(dictionary.entries().isEmpty());  

No comments:

Post a Comment