Showing posts with label Spring Load Resource from file. Show all posts
Showing posts with label Spring Load Resource from file. Show all posts

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());