そこで、Tokenizerクラスを作成し、Filter、charFilter を柔軟に設定できる fieldType を作成します。
形態素解析には、同様に Sen を使用することとします。
TokenizerFactory を作る時は、「 org.apache.solr.analysis.BaseTokenizerFactory.class 」を実装し、
Factoryメソッドで、Tokenizer を作成します。
この辺りの流れは、ドキュメントを見るか、実際の solr や lucene のソースを見るのが参考になります。
JapaneseAnalyzer は、以下の流れで解析しています。
Reader を、NormalizeReader でラップし、全角⇔半角に文字を寄せる。
設定フィルで記述した tokenizerClass のインスタンスを作成。
POSFilter - 不要な型の term を除去
DigitFIlter - Senによって分割された数字を結合
LowerCaseFilter - 大文字を小文字に変換
KatakanaStemFilter - 4文字以降のカタカナの最後の「ー」を除去
StopFilter - stopwords を削除
設定フィルで記述した tokenizerClass のインスタンスを作成。
POSFilter - 不要な型の term を除去
DigitFIlter - Senによって分割された数字を結合
LowerCaseFilter - 大文字を小文字に変換
KatakanaStemFilter - 4文字以降のカタカナの最後の「ー」を除去
StopFilter - stopwords を削除
最終的にはこのような fieldType を作成することを目標に、まずは、Tokenizerを作成します。
JapaneseAnalyzerを使うときは、システムプロパティからsen.home の値を取得しますが、
Solr 入門の本や、org.apache.solr.core.SolrResourceLoader.class の様に、
「 JNDI -> System Property -> デフォルト 」
とチェックするのがいいと思うので、そのように実装し、
sen/home は web.xml に記述することにします。
ただし、JapaneseAnalyzer を使う場合は、必ずシステムプロパティが必要なので、
同じ設定を2箇所に記述するよりは、システムプロパティに統一するほうがよさそうです。
そんなこんなで、Factoryクラスのソースは以下のような感じになります。
public class SenTokenizerFactory extends BaseTokenizerFactory { private static final Logger log = LoggerFactory.getLogger(SenTokenizerFactory.class); static final String PROP_SEN_HOME = "sen.home"; static final String JNDI_SEN_HOME = "sen/home"; static final String FS = System.getProperty("file.separator"); static final String SEN_XML = FS + "conf" + FS + "sen.xml"; String configFile; String compositRule; @Override public void init(Mapargs) { String senHome = null; // Try JNDI try { Context c = new InitialContext(); senHome = (String)c.lookup("java:comp/env/" + JNDI_SEN_HOME); log.info("Using JNDI sen/home: " + senHome); } catch (NoInitialContextException e) { log.info("JNDI not configured for Solr (NoInitialContextEx)"); } catch (NamingException e) { log.info("No sen/home in JNDI"); } catch (RuntimeException ex) { log.warn("Odd RuntimeException while testing for JNDI: " + ex.getMessage()); } // Now try system property if (senHome == null){ senHome = System.getProperty(PROP_SEN_HOME); log.info("Using System property sen.home: " + senHome); } // Set current path if (senHome == null) { senHome = "."; log.info("sen.home defaulted to '.' (could not find system property or JNDI)"); } configFile = senHome + SEN_XML; log.info( "config file for SenTokenizer is " + configFile ); readConfig(); log.info("conpositRule is: " + (compositRule == null ? "NULL" : compositRule)); } protected String getConfigFile() { return configFile; } private void readConfig() { List<String> compositRuleList = new ArrayList<String>(); try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new InputSource(configFile)); NodeList nl = doc.getFirstChild().getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { org.w3c.dom.Node n = nl.item(i); if (n.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { String nn = n.getNodeName(); String value = n.getFirstChild().getNodeValue(); if (nn.equals("composit")) { compositRuleList.add(value); log.info("add composit rule: " + value); } } } if (compositRuleList.size() > 0) { compositRule = StringUtils.join(compositRuleList, "\n"); } } catch (ParserConfigurationException e) { throw new IllegalArgumentException(e.getMessage()); } catch (FileNotFoundException e) { throw new IllegalArgumentException(e.getMessage()); } catch (SAXException e) { throw new IllegalArgumentException(e.getMessage()); } catch (IOException e) { throw new IllegalArgumentException(e.getMessage()); } } public Tokenizer create(Reader input) { try { return new SenTokenizer(input, configFile, compositRule); } catch (IOException e) { throw new RuntimeException("cannot initialize SenTokenizer: " + e.toString()); } } }
そして、SenTokenizerクラスは以下のように。
public class SenTokenizer extends Tokenizer { private StreamTagger tagger = null; private String configFile = null; private String compositRule = null; private static final HashSethash = new HashSet (); public SenTokenizer(Reader input, String configFile, String compositRule) throws IOException { super(input); this.configFile = configFile; this.compositRule = compositRule; init(input); } private void init(Reader input) throws IOException { tagger = new StreamTagger(input, configFile); synchronized(hash) { if (compositRule != null && !compositRule.equals("")) { if (!hash.contains(compositRule)) { CompositPostProcessor p = new CompositPostProcessor(); p.readRules(new BufferedReader(new StringReader( compositRule))); hash.add(compositRule); tagger.addPostProcessor(p); } } } } public Token next(Token token) throws IOException { if (!tagger.hasNext()) return null; net.java.sen.Token t = tagger.next(); if (t == null) return next(token); return token.reinit( t.getBasicString(), correctOffset(t.start()), correctOffset(t.end()), t.getPos()); } @Override public void reset(Reader input) throws IOException { super.reset(input); init(input); } }
これで、fieldType の以下の部分までができました。
<fieldType name="text_ja" class="solr.TextField"> <analyzer> <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ja.txt" /> <tokenizer class="mydomain.SenTokenizerFactory" /> </analyzer> </fieldType>
追記:
2/18 sen.xml に、composit の設定を追加した際の処理をソースに追記しました。
まだマルチスレッドでうまく動作するかはテスト中です。
参考サイト
http://d.hatena.ne.jp/bowez/20090513
参考本
No comments:
Post a Comment