Lucene (http://lucene.apache.org/) João Kramer ([email protected]) Tribunal de Contas da União Características • Lucene é uma API de indexação e pesquisa. • Foi inicialmente codificado por Doug Cutting em 1997. • Em 2000 estava no SourceForge. • Em 2004 foi integrado ao projeto Apache. • Esta codificado em Java, Delphi, Ruby, PHP,C++, C#, Perl e Python. O que é um Search Engine? • Resposta: Um software que cria um índice para um conjunto de documentos e responde a consultas usando esse índice. • Mas um banco de dados faz isso! O que é um Search Engine? • Um Search Engine oferece: – Escalabilidade – Pesquisa ranqueada (Relevance Ranking) – Integra diferentes fontes de dados (email, páginas web, arquivos, banco de dados, ...) – Trabalha em palavras, não em substrings – Processo de indexação • Converte documentos • Extrai texto e metadados • Normaliza texto • Cria o índice invertido Lucene Java • • • • • • • • Biblioteca para indexação e pesquisa Sem dependências Java 1.4 ou superior Entrada para indexação são instâncias de Document contendo Fields (field name; field content) Pesquisa usa objetos do tipo Query Armazena os índice como arquivos no disco Não tem conversor de documentos (Nutch) Não tem web crawler (Nutch) Lucene Java • Sintaxe de query poderosa • Pode-se criar query de forma programática ou diretamente dos dados informados pelo usuário. • Indexação rápida • Pesquisa rápida • Sort por relevância ou outros campos • Grande comunidade (bastante ativa) Vai começar ... Criando um índice (Fácil!!!) • Diretório do índice File indexDir = new File(“c:\\lucene\\index”); • Criando o objeto indexador IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true); • Indexando Document doc = new Document(); String texto = “The quick brown fox jumps over the lazy dog”; doc.add(Field.Text(“conteudo”,texto); doc.add(Field.Text(“data”, (new Date()).toString()); writer.addDocument(doc); writer.close(); Pesquisando • Diretório do índice Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false); • Criando o objeto de pesquisa IndexSearcher is = new IndexSearcher (indexDir); • Pesquisando String pesquisa = “fox”; Query query = new QueryParser.parse(pesquisa,”conteudo”,new StandardAnalyzer()); Hits hits = is.search(query); for (int i=0; i < hits.length(); i++) { Document doc = hits.doc(i); System.out.println(doc.get(“conteudo”)); System.out.println(“indexado em “ + doc.get(“data”)); } Classes de indexação e pesquisa • Classes fundamentais do processo de indexação e pesquisa Classes de indexação - IndexWriter • • • • Cria um novo índice. Adiciona documentos a um índice. Não permite ler o índice ou pesquisá-lo. Usada para modificar o índice. File indexDir = new File(“c:\\lucene\\index”); IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true); Classes de indexação e pesquisa Directory • Representa o local do índice. • É uma classe abstrata. • FSDirectory implementação que acessa os arquivos do índice no sistema operacional. • RAMDirectory implementação que mantém os dados do índice em memória. Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false); Classes de indexação e pesquisa Analyzer • Antes do texto ser indexado, ele é processado por um analisador. • O analisador pode retirar ‘palavras’ (tokens), eliminar tokens, transformar para minúscula, retirar ‘stop words’. Em geral permite realizar qualquer funcionalidade programável. • Implementações BrazilianAnalyser; WhitespaceAnalyzer; SimpleAnalyzer; StopAnalyzer; StandardAnalyzer; File indexDir = new File(“c:\\lucene\\index”); IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true); Classes de indexação e pesquisa Document • Representa um coleção de campos (fields). • O campos (fields) representam o documento ou seus metadados. • O Lucene apenas indexa texto (String ou Reader) Document doc = new Document(); String texto = “The quick brown fox jumps over the lazy dog”; doc.add(Field.Text(“conteudo”,texto); doc.add(Field.Text(“data”, (new Date()).toString()); Classes de indexação e pesquisa - Field • Um documento contêm um ou mais campos (fields) encapsulados na classe Field. • Existem quatro tipos de Field: – Keyword não é analisado, mas é indexado e armazenado no índice (ex: URL, datas, CPF, nomes pessoais). Aceita campos String e Date. – UnIndexed não é analisado nem indexado, mas é armazenado no índice (ex:PK). – UnStored é analisado e indexado, mas não é armazenado no índice (mais usado). – Text é analisado e indexado; se do tipo String é armazenado, mas se do tipo Reader não. Classes de indexação e pesquisa - IndexSearcher • Abre o índice no modo read-only e permite pesquisá-lo. Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false); IndexSearcher is = new IndexSearcher (indexDir); String pesquisa = “fox”; Query query = new QueryParser.parse(pesquisa,”conteudo”,new StandardAnalyzer()); Hits hits = is.search(query); Classes de indexação e pesquisa - Term • É a unidade básica de pesquisa. • Similar ao Field, consiste num par de strings: o nome do campo a pesquisar e o valor a ser pesquisado. • Também é usada no processo de indexação. Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false); IndexSearcher is = new IndexSearcher (indexDir); String pesquisa = “fox”; Query query = new TermQuery(new Term(”conteudo”,pesquisa)); Hits hits = is.search(query); Classes de indexação e pesquisa - Query • Lucene vem com algumas subclasses concretas de Query. • Ex: BooleanQuery, PhraseQuery, PrefixQuery, PhraseQuery, PrefixQuery, PhrasePrefixQuery, RangeQuery, FilteredQuery e SpanQuery Classes de indexação e pesquisa TermQuery • É o tipo mais básico de Query suportado pelo Lucene. • É usada para encontrar documentos que contém campos com um determinado valor. Classes de indexação e pesquisa - Hits • É um container que contém ponteiros para objetos do tipo Document resultantes de uma pesquisa ranqueada (os documentos são ranqueados). • Por questões de performance as instâncias de Hits não carregam do índice todas as instâncias de Document. Funciona por demanda, em blocos de 100 documentos. Classes de indexação e pesquisa Exemplo • Exemplo1 Processo de indexação Processo de indexação • Três passos: – Conversão para texto. – Análise – Gravar no índice • Índice invertido • Ao invés de perguntar “que palavra tem no documento”, pergunta-se “que documentos contém determinada palavra”. • Os tokens são as chaves que apontam para o documento. Processo de indexação • Documentos heterogêneos – É possível que documentos com diferentes conjuntos de campos possam coexistir no mesmo índice. – Ex: documentos de acórdão com número do processo, inteiro teor, e ministro relator; currículos com CPF, nome, data nascimento e experiência profissional. Processo de indexação • Adicionando campos String sinonimos[] = new String {“menino”,”garoto”,”guri”}; Document doc = new Document(); for (int i=0; i < sinonimos.length ; i++) { doc.add(Field.Text(“palavra”,sinonimos[i])); } -------------------------------------------------------------------------------------------String sinonimos = “menino garoto guri” ; Document doc = new Document(); doc.add(Field.Text(“palavra”,sinonimos)); Processo de indexação • Removendo e recuperando documentos IndexReader reader = new IndexReader.open(dir); reader.delete (1); reader.delete(new Term(“id”,”1”)); reader.delete(new Term(“CPF”,”32000227104”)); // deve-se usar a mesma instância do IndexReader reader.undeleteAll(); Processo de indexação • Boost factor Document doc = new Document(); doc.add(Field.Keyword(“senderEmail”,getSenderEmail())); doc.add(Field.Keyword(“senderName”,getSenderName())); doc.add(Field.Keyword(“subject”,getSubject())); doc.add(Field.Text(“body”,getBody())); if (getSenderDomain().endsWithIgnoreCase(COMPANY_DOMAIN)) { doc.setBoost(1.5); } else { doc.setBoost(0.1); } writer.addDocument(doc). Processo de indexação • Boost factor – O “boost fator” é considerado no momento da busca. Na pesquisa, os documentos recebem uma pontuação que leva em conta quão compatíveis estão com a query, além dos valores dos “boost fator”. – O “boost factor” do documento se aplica a todos os campos desse. – O “boost fator” do campo (Field.setBoost) é considerado em conjunto com o do documento ao qual pertence. Processo de indexação • Indexando datas – Antes de ser indexado, o campo é convertido para uma String utilizando a classe DateField. – Recomenda-se converter para String no formato YYYYMMDD e indexar como String. Document doc = new Document(); doc.add(Field.Keyword(“data”,new Date())); Processo de indexação • Indexando números – Devem ser convertidos para String e preparados (ex: 0001, 0010). Índice - Performance • Ajustando a performance Variável IndexWriter Valor Padrão Descrição mergeFactor 10 Controla a freqüência do merge e tamanho dos segmentos maxMergeFactor Integer.MAX_VALUE Limita o número de documentos por segmento minMergeDocs 10 Controla a quantidade de RAM usada ao indexar Índice - Performance • Ajustando a performance – Se o mergeFactor for 10, um novo segmento é criado para cada 10 documentos adicionados ao índice. – Quando o 10º segmento é adicionado, todos os 10 segmentos são concatenados num único segmento de com 100 documentos. – Quando 10º segmento de 100 documentos for criado, novamente os segmentos serão concatenados em 1 segmento de 1000. Índice - Performance • Ajustando a performance – Nunca existirão mais de 9 segmentos no índice e o tamanho de cada segmento é uma potência de 10. – Exceção: se o maxMergeDocs for 1.000, ao invés de criar um segmento de 10.000, será criado o 11º segmento de 1000. – minMergeDocs controla quantos documentos devem ser mantidos em memória antes de serem gravados no segmento. Índice - Exemplo • Exemplo 2 Índice - RAMDirectory • Indexação na memória (RAMDirectory) IndexWriter writer = new IndexWriter(new RamDirectory(), new SimpleAnalyzer(),true); writer.mergeFactor = x; writer.maxMergeDocs = y; writer.minMergeDocs = z; Índice - Batch • Indexação em batch FSDirectory fsDir = FSDirectory.getDirectory("/tmp/index“,true); RAMDirectory ramDir = new RAMDirectory(); IndexWriter fsWriter = IndexWriter(fsDir,new SimpleAnalyzer(), true); IndexWriter ramWriter = new IndexWriter(ramDir,new SimpleAnalyzer(), true); while (existe documento para indexar) { ... criar Document ... ramWriter.addDocument(doc); if (condition for flushing memory to disk has been met) { fsWriter.addIndexes(Directory[] {ramDir}); ramWriter.close(); ramWriter = new IndexWriter(ramDir, new SimpleAnalyzer(),true); } } Manutenção do índice • Otimização do índice – Quanto menos arquivos de índice menor o tempo de pesquisa. Não minimiza o tempo de indexação! – Segundo o livro, incluir documentos num índice não otimizado é mais rápido que incluí-los num índice otimizado (why!?) – O tempo de pesquisa é reduzido em função da menor quantidade de arquivos a ler. Índice - Otimização • Otimização do índice IndexWriter writer = new IndexWriter("/path/to/index“,analyzer, false); writer.optimize(); writer.close(); Índice – Concorrência • Regras de concorrência – Qualquer número de operações read-only podem ser executadas de forma concorrente. Várias threads e processos podem pesquisar no índice ao mesmo tempo. – Qualquer número de operações read-only podem ser executadas durante a atualização do índice (otimização, adição de novos documentos, atualização ou deleção de documentos). – Somente um operação de alteração de índice por vez. Índice - Concorrência • Regras de concorrência Operação Se permitida Executar pesquisas simultâneas no mesmo índice. Sim Executar pesquisas simultâneas num índice que está sendo criado, otimizado, concatenado com outro índice, ou cujos documentos estão sendo apagados ou atualizados. Sim Adicionar ou atualizar documentos no mesmo índice usando múltiplas instâncias de IndexWriter. Não Esquecer de fechar o IndexReader usado para apagar documentos a um índice, antes de abrir um IndexWriter para adicionar mais documentos ao mesmo índice. Não Esquecer de fechar o IndexWriter usado para adicionar documentos a um índice, antes de abrir um IndexReader para apagar documentos desse mesmo índice. Índice – Concorrência • Regras de concorrência – Operações com uma instância de IndexWriter ou IndexReader compartilhadas entre várias threads. Query Read doc Add Delete Optimize Merge Query (R) Read doc (R) Add (W) Delete (R) x x x x Optimize (W) x Merge (W) x Índice – Concorrência • Regras de concorrência – Uma operação de modificação do índice através do IndexReader não pode ser feita se uma operação de modificação de índice através do IndexWriter estiver em progresso, e vice-versa. Índice – Concorrência Lock File Classe Obtido em Apagado em Descrição write.lock IndexWriter Construtor close() Lock apagado quando o IndexWriter é fechado. write.lock IndexReader delete(int) close() Lock apagado quando o IndexReader é fechado. write.lock IndexReader undeleteAll(int) close() Lock apagado quando o IndexReader é fechado. write.lock IndexReader setNorms(int, String, byte) close() Lock apagado quando o IndexReader é fechado. commit.lock IndexWriter Constructor Construtor Lock apagado assim que a informação do segmento é lida ou gravada. commit.lock IndexWriter addIndexes (IndexReader[]) addIndexes (IndexReader[]) Lock obtido quando um novo segmento é gravado. commit.lock IndexWriter addIndexes (Directory[]) addIndexes (Directory[]) Lock obtido quando um novo segmento é gravado. commit.lock IndexWriter mergeSegments (int) mergeSegments (int) Lock obtido quando um novo segmento é gravado. commit.lock IndexReader open(Directory) Open(Directory) Lock obtido até que todos os segmentos sejam lidos. commit.lock SegmentReader doClose() doClose() Lock obtido enquanto o segmento é gravado ou regravado. commit.lock SegmentReader undeleteAll() undeleteAll() Lock obtido enquanto o arquivo de segmento .del é apagado. Pesquisa Pesquisa – Principais classes Classe Objetivo IndexSearcher Todas as pesquisas são feitas através de uma instância de IndexSearch usando um dos métodos sobrecarregados search. Query e subclasses Classes concretas de Query encapsulam a lógica para um determinado tipo de consulta. Essas instâncias são passadas para o método search de IndexSearch. QueryParser Processa expressões de pesquisa e gera uma classe concreta filha de Query. Hits Provê acesso aos resultados das pesquisas. Um instância de Hits é retornada pelo método search de IndexSearch. Pesquisa - Exemplo public class SearchingTest extends LiaTestCase { public void testTerm() throws Exception { IndexSearcher searcher = new IndexSearcher(directory); Term t = new Term("subject", "ant"); Query query = new TermQuery(t); Hits hits = searcher.search(query); assertEquals("JDwA", 1, hits.length()); t = new Term("subject", "junit"); hits = searcher.search(new TermQuery(t)); assertEquals(2, hits.length()); searcher.close(); } } Pesquisa – Exemplo • É importante que os termos passados para o IndexSearcher sejão consistentes (compatíveis) com os termos produzidos pelo analisador ao indexar o documento. • Hints os documentos são recuperados sob demanda. Pesquisa - QueryParser • Fazer o ‘parse’ de uma consulta de usuário (ex: bola OR goleiro) corresponde a transformá-la numa instância apropriada de Query. • O objeto QueryParser requer uma analizador (analyzer) para quebrar a consulta em termos (terms). • Os outros tipos de Query não requerem um analizador, entretanto os termos pesquisados devem corresponder ao que foi indexado. Pesquisa – QueryParser (exemplo) public void testQueryParser() throws Exception { IndexSearcher searcher = new IndexSearcher(directory); Query query = QueryParser.parse("+JUNIT +ANT MOCK“,"contents“,new SimpleAnalyzer()); Hits hits = searcher.search(query); assertEquals(1, hits.length()); Document d = hits.doc(0); assertEquals("Java Development with Ant", d.get("title")); query = QueryParser.parse("mock OR junit“,"contents“,new SimpleAnalyzer()); hits = searcher.search(query); assertEquals("JDwA and JIA", 2, hits.length()); } Pesquisa – QueryParser (expressões) Expressão da Query Encontra documentos que ... java Contêm java no campo default. java junit Java or junit Contêm o termo java ou junit, ou ambos, no campo default. +java +junit java AND junit Contêm java e junit no campo default. title:ant Contêm o termo ant no campo title. title:extreme –subject:sports title:extreme AND NOT subject:sports Têm extreme no campo title e não tem sports no campo subject (agile OR extreme) AND methodology Contêm methodology e deve conter agile e/ou extreme, todos no campo default. title:”junit in action” Contêm a frase (exata) ‘junit in action’ no campo title title:”junit action”~5 Contêm os termos junit e action a menos de 5 posições de distância um do outro java* Contêm termos que começam com java no campo default. java~ Contêm termos que são semelhantes (próximos) a java, como ‘lava’ lastmodified:[1/1/04 TO 12/31/04] Têm no campo lastmodified valores de data entre 01/01/2004 e 31/12/04. Pesquisa - IndexSearcher • O último argumento do método FSDirectory.getDirectory() sinaliza a abertura do índice, e não a construção de um novo. public abstract class LiaTestCase extends TestCase { private String indexDir = System.getProperty("index.dir"); protected Directory directory; protected void setUp() throws Exception { directory = FSDirectory.getDirectory(indexDir,false); } // ... } Pesquisa – IndexSearcher • Uma instância de IndexSearcher pesquisa o índice somente com os valores existentes no momento da abertura. Se está ocorrendo uma indexação concorrentemente com a pesquisa, os novos documentos não serão visíveis. Para visualizar os novos documentos uma nova instância de IndexSearcher deve ser criada. Pesquisa - Hits Métodos do Hits Retorno length() Número de documentos na coleção Hits. doc(n) Instância do documento (Document) na posição “n”. Os documento são ordenados pela pontuação que recebem. id(n) ID do documento na posição “n”. score(n) Pontuação normalizada (baseada na pontuação do maior documento). Os pontos ficam entre 0 e 1. Pesquisa - Hits • O objeto Hits mantém um número limitado de documentos na memória e além de uma lista dos documentos mais acessados. Os 100 primeiros documentos são automaticamente recuperados e colocados no cache. • Os métodos doc(n), id(n) e score(n) forçam a leitura do documento no índice se esses ainda não estão no cache. Pesquisa – Hits- Paginação • Mantenha as instâncias originais de Hits e IndexSearcher ao navegar pelos resultados. • Refaça a consulta sempre que o usuário navegar para uma nova página (pode parecer lento, mas não é). Pesquisa - Scoring Factor Description tf(t in d) Term frequency factor for the term (t) in the document (d). idf(t) Inverse document frequency of the term. boost(t.field in d) Field boost, as set during indexing. lengthNorm(t.field in d) Normalization value of a field, given the number of terms within the field. This value is computed during indexing and stored in the index. coord(q, d) Coordination factor, based on the number of query terms the document contains. queryNorm(q) queryNorm(q) Normalization value for a query, given the sum of the squared weights of each of the query terms. Pesquisa - TermQuery • Um termo (Term) é a menor parte do índice, e consiste de um nome de campo e um valor. public void testKeyword() throws Exception { IndexSearcher searcher = new IndexSearcher(directory); Term t = new Term("isbn", "1930110995"); Query query = new TermQuery(t); Hits hits = searcher.search(query); assertEquals("JUnit in Action", 1, hits.length()); } Pesquisa - RangeQuery • Facilita a pesquisa de um termo inicial a um termo final. • O terceiro argumento do construtor RangeQuery indica se o intervalo especificado é inclusivo ou não ([] ; {}) public class RangeQueryTest extends LiaTestCase { private Term begin, end; protected void setUp() throws Exception { begin = new Term("pubmonth","198805"); // pub date of TTC was October 1988 end = new Term("pubmonth","198810"); super.setUp(); } public void testInclusive() throws Exception { RangeQuery query = new RangeQuery(begin, end, true); IndexSearcher searcher = new IndexSearcher(directory); Hits hits = searcher.search(query); assertEquals("tao", 1, hits.length()); } } Pesquisa - PrefixQuery • Encontra documentos que contenham termos começando com uma determinada String. public class PrefixQueryTest extends LiaTestCase { public void testPrefix() throws Exception { IndexSearcher searcher = new IndexSearcher(directory); Term term = new Term("category“, "/technology/computers/programming"); PrefixQuery query = new PrefixQuery(term); Hits hits = searcher.search(query); int programmingAndBelow = hits.length(); hits = searcher.search(new TermQuery(term)); int justProgramming = hits.length(); assertTrue(programmingAndBelow > justProgramming); } } Pesquisa - BooleanQuery • Uma cláusula é que pode ser opcional, requerida ou proibida. Pesquisa - BooleanQuery public void testAnd() throws Exception { TermQuery searchingBooks =new TermQuery(new Term("subject","search")); RangeQuery currentBooks =new RangeQuery(new Term("pubmonth","200401"), new Term("pubmonth","200412"),true); BooleanQuery currentSearchingBooks = new BooleanQuery(); currentSearchingBooks.add(searchingBook s, true, false); currentSearchingBooks.add(currentBooks, true, false); IndexSearcher searcher = new IndexSearcher(directory); Hits hits = searcher.search(currentSearchingBooks); assertHitsIncludeTitle(hits, "Lucene in Action"); } // following method from base LiaTestCase class protected final void assertHitsIncludeTitle(Hits hits, String title) throws IOException { for (int i=0; i < hits.length(); i++) { Document doc = hits.doc(i); if (title.equals(doc.get("title"))) { assertTrue(true); return; } } fail("title '" + title + "' not found"); } Pesquisa - BooleanQuery required prohibited false true false clause is optional clause must match true clause must not match Invalid Pesquisa - PhraseQuery • O índice contém informação sobre a posição dos termos e a classe PhraseQuery usa essa informação para determinar a distância entre os termos. The quick brown fox jumped over the lazy dog (quick fox) 1 (fox quick) 3 (quick,jumped,lazy) 4 (lazy,jumped,quick) 8 Pesquisa - PhraseQuery public class PhraseQueryTest extends TestCase { private IndexSearcher searcher; protected void setUp() throws IOException { // set up sample document RAMDirectory directory = new RAMDirectory(); IndexWriter writer = new IndexWriter(directory, new WhitespaceAnalyzer(), true); Document doc = new Document(); doc.add(Field.Text("field“,"the quick brown fox jumped over the lazy dog")); writer.addDocument(doc); writer.close(); searcher = new IndexSearcher(directory); } private boolean matched(String[] phrase, int slop) throws IOException { PhraseQuery query = new PhraseQuery(); query.setSlop(slop); for (int i=0; i < phrase.length; i++) { query.add(new Term("field", phrase[i])); } Hits hits = searcher.search(query); return hits.length() > 0; } } Pesquisa - PhraseQuery public void testSlopComparison() throws Exception { String[] phrase = new String[] {"quick", "fox"}; assertFalse("exact phrase not found", matched(phrase, 0)); assertTrue("close enough", matched(phrase, 1)); } public void testMultiple() throws Exception { assertFalse("not close enough“,matched(new String[] {"quick", "jumped", "lazy"}, 3)); assertTrue("just enough“,matched(new String[] {"quick", "jumped", "lazy"}, 4)); assertFalse("almost but not quite“,matched(new String[] {"lazy", "jumped", "quick"}, 7)); assertTrue("bingo“,matched(new String[] {"lazy", "jumped", "quick"}, 8)); } Pesquisa - WildcardQuery • Caracter ‘*’ para representar zero ou mais caracteres • Caracter ‘?’ para representar zero ou um caracter. Pesquisa - WildcardQuery private void indexSingleFieldDocs(Field[] fields) throws Exception { IndexWriter writer = new IndexWriter(directory,new WhitespaceAnalyzer(), true); for (int i = 0; i < fields.length; i++) { Document doc = new Document(); doc.add(fields[i]); writer.addDocument(doc); } writer.optimize(); writer.close(); } public void testWildcard() throws Exception { indexSingleFieldDocs(new Field[] { Field.Text("contents", "wild"),Field.Text("contents", "child"), Field.Text("contents", "mild"),Field.Text("contents", "mildew") }); IndexSearcher searcher = new IndexSearcher(directory); Query query = new WildcardQuery(new Term("contents", "?ild*")); Hits hits = searcher.search(query); assertEquals("child no match", 3, hits.length()); assertEquals("score the same", hits.score(0), hits.score(1), 0.0); assertEquals("score the same", hits.score(1), hits.score(2), 0.0); } Pesquisa - FuzzyQuery • Procura por termos semelhantes. public void testFuzzy() throws Exception { indexSingleFieldDocs(new Field[] {Field.Text("contents", "fuzzy"), Field.Text("contents", "wuzzy“)}); IndexSearcher searcher = new IndexSearcher(directory); Query query = new FuzzyQuery(new Term("contents", "wuzza")); Hits hits = searcher.search(query); assertEquals("both close enough", 2, hits.length()); assertTrue("wuzzy closer than fuzzy", hits.score(0) != hits.score(1)); assertEquals("wuzza bear“,"wuzzy", hits.doc(0).get("contents")); } Pesquisa - Exemplo • Exemplo 3 Analyzer • Durante o processo de análise pode-se remover palavras, acentos e palavras comuns (stop words), descartar pontuação ou reduzir palavras a forma ‘primitiva’ (stemming). Esse processo é denominado ‘tokenization’ e os texto resultantes dele são chamados de ‘tokens’. • A combinação dos ‘tokens’ ao nome do campo é denominado ‘terms’. Analyzer – Exemplo Analyzing "The quick brown fox jumped over the lazy dogs" WhitespaceAnalyzer: [The] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] SimpleAnalyzer: [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] StopAnalyzer: [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] StandardAnalyzer: [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] Analyzing "XY&Z Corporation - [email protected]" WhitespaceAnalyzer: [XY&Z] [Corporation] [-] [[email protected]] SimpleAnalyzer: [xy] [z] [corporation] [xyz] [example] [com] StopAnalyzer: [xy] [z] [corporation] [xyz] [example] [com] StandardAnalyzer: [xy&z] [corporation] [[email protected]] Analyzer -Exemplo • Exemplo 4 Analyzer -Escopo • Índice Analyzer analyzer = new StandardAnalyzer(); IndexWriter writer = new IndexWriter(directory, analyzer, true); • Documento writer.addDocument(doc, analyzer); • Durante a análise tem-se acesso ao nome do campo, portanto pode-se utilizar essa informação para analisar o texto associado ao campo de forma específica. Analyzer - Parsing • A análise não é parser. • Um documento HTML deve ter as tags removidas antes de ser submetido ao analisador. Analyzer -Classe • A classe abstrata Analyzer é a base. • Transforma o texto numa stream de tokens (não é terms) chamada de TokenStream. public final class SimpleAnalyzer extends Analyzer { public TokenStream tokenStream(String fieldName, Reader reader) { return new LowerCaseTokenizer(reader); } } • O TokenStream se parece com a classe Enumerator, ou seja retornar sucessivos Tokens a cada chamada do método next() e retorna null ao final. Analyzer - Token • É representado pela classe Token e tem como atributos: ‘start offset’ , ‘end offset’, ‘position increment’, texto e tipo. the quick brown fox • 1: [the:0->3:word] • 2: [quick:4->9:word] • 3: [brown:10->15:word] • 4: [fox:16->19:word] Analyzer - Estrutura Analyzer - Estrutura Analyzer -Tokenizer • A fonte de dados é um Reader. • Sobrescreve o método next(Token t) de TokenStream. Analyzer -TokenStream public abstract class TokenStream { // método chamado pelo indexador public Token next() throws IOException { Token result = next(new Token()); if (result != null) { Payload p = result.getPayload(); if (p != null) { result.setPayload((Payload) p.clone()); } } return result; } // métodos a serem sobrescritos public Token next(Token result) throws IOException { return next(); } public void reset() throws IOException {} public void close() throws IOException {} } Analyzer - Tokenizer public abstract class Tokenizer extends TokenStream { /** The text source for this Tokenizer. */ protected Reader input; /** Construct a tokenizer with null input. */ protected Tokenizer() {} /** Construct a token stream processing the given input. */ protected Tokenizer(Reader input) { this.input = input; } /** By default, closes the input Reader. */ public void close() throws IOException { input.close(); } /** Expert: Reset the tokenizer to a new reader. Typically, an * analyzer (in its reusableTokenStream method) will use * this to re-use a previously created tokenizer. */ public void reset(Reader input) throws IOException { this.input = input; } } Analyzer - CharTokenizer public abstract class CharTokenizer extends Tokenizer { public CharTokenizer(Reader input) { super(input); } protected abstract boolean isTokenChar(char c); protected char normalize(char c) { return c; } public final Token next(Token token) throws IOException { token.clear(); int length = 0; int start = bufferIndex; char[] buffer = token.termBuffer(); while (true) { if (bufferIndex >= dataLen) { offset += dataLen; dataLen = input.read(ioBuffer); if (dataLen == -1) { .... } } Analyzer - WhitespaceTokenizer public class WhitespaceTokenizer extends CharTokenizer { /** Construct a new WhitespaceTokenizer. */ public WhitespaceTokenizer(Reader in) { super(in); } /** Collects only characters which do not satisfy * {@link Character#isWhitespace(char)}.*/ protected boolean isTokenChar(char c) { return !Character.isWhitespace(c); } } Analyzer - Classe public abstract class Analyzer { // o indexador irá chamar o método next() de TokenStream public abstract TokenStream tokenStream(String fieldName, Reader reader); public TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException { return tokenStream(fieldName, reader); } private ThreadLocal tokenStreams = new ThreadLocal(); protected Object getPreviousTokenStream() { return tokenStreams.get(); } protected void setPreviousTokenStream(Object obj) { tokenStreams.set(obj); } public int getPositionIncrementGap(String fieldName) { return 0; } } Analyzer - WhitespaceAnalyzer public final class WhitespaceAnalyzer extends Analyzer { public TokenStream tokenStream(String fieldName, Reader reader) { return new WhitespaceTokenizer(reader); } public TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException { Tokenizer tokenizer = (Tokenizer) getPreviousTokenStream(); if (tokenizer == null) { tokenizer = new WhitespaceTokenizer(reader); setPreviousTokenStream(tokenizer); } else tokenizer.reset(reader); return tokenizer; } } Analyzer public final class BrazilianAnalyzer extends Analyzer { public final TokenStream tokenStream(String fieldName, Reader reader) { TokenStream result = new StandardTokenizer(reader); // remove "'s" and "." result = new StandardFilter(result); // remove stop words result = new StopFilter(result, stoptable); // do brazilian steamming result = new BrazilianStemFilter(result, excltable); return result; } } Analyzer • Ver BrazilianAnalyzer Documentos “Indexáveis” • HTML (CyberNeko HTML Parser, JTidy, TagSoup,Jericho HTML Parser, TextExtractor) • XML (deve-se fazer o parse) • Arquivos Openoffice são arquivos ZIP que contêm um arquivo ‘meta.xml’ e outro ‘content.xml’. • Word, Excel, PowerPoint, Vision usar o POI Documentos “Indexáveis • Email http://www.tropo.com/techno/java/lucene/i map.html e http://guests.evectors.it/zoe/ • RTF Usar classe SwingRTFEditorKit • PDF PDFBox, XPDF e JPedal. Solução TCU • GED (900 mil documentos ; incluindo as versões) • Documentos incluídos no GED são automaticamente indexados pelo Lucene • Aplicativo em Swing (Sisdoc) usa dados relacionais e do Lucene nas consultas • Aplicativos corporativos alimentam o GED. Solução TCU Quem usa • Wikipedia • TCU • Apple Mac OS Finder (correspondente ao Microsoft Explorer) • Enciclopédia Britânica (CD / DVD) • FedEx • Eclipse Bibliografia • “Lucene in Action” de Otis Gospodnetic & Erik Hatcher • http://wiki.apache.org/lucenejava/LuceneFAQ • http://lucene.apache.org/