そこで、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(Map args) {
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 HashSet hash = 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