/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * Copyright 2014 Geomatys. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.constellation.admin.index.impl; import org.apache.commons.lang3.StringUtils; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.IntField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.NoLockFactory; import org.apache.lucene.util.Version; import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.util.logging.Logging; import org.constellation.admin.exception.ConstellationException; import org.constellation.admin.index.IndexEngine; import org.constellation.configuration.ConfigDirectory; import org.constellation.utils.MetadataFeeder; import org.geotoolkit.lucene.analysis.standard.ClassicAnalyzer; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import org.apache.lucene.search.NumericRangeQuery; /** * Implementation of {@link IndexEngine} with Lucene. * * @author Christophe Mourette (Geomatys). * @author Mehdi Sidhoum (Geomatys). */ @Component public class LuceneIndexEngine implements IndexEngine { /** * Used for debugging purposes. */ private static final Logger LOGGER = Logging.getLogger("org.constellation.admin.index.impl"); private IndexWriter indexWriter; private IndexSearcher indexSearcher; @PostConstruct private void init() throws IOException { createIndex(); initIndexSearcher(); } private void createIndex() throws IOException { final File dataIndexDirectory = ConfigDirectory.getDataIndexDirectory(); final Directory directory = FSDirectory.open(dataIndexDirectory, NoLockFactory.getNoLockFactory()); final ClassicAnalyzer analyzer = new ClassicAnalyzer(Version.LUCENE_4_9); final IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_9, analyzer); indexWriter = new IndexWriter(directory, config); indexWriter.commit(); } private void initIndexSearcher() throws IOException { DirectoryReader directoryReader = DirectoryReader.open(indexWriter.getDirectory()); indexSearcher = new IndexSearcher(directoryReader); } @PreDestroy private void destroy() throws IOException { LOGGER.info("closing metadata index"); indexWriter.close(); } /** * Adding metadata for given data Id into Lucene index. * @param metadata given metadata object to index. * @param dataId data Id. */ @Override public void addMetadataToIndexForData(final DefaultMetadata metadata,final Integer dataId) throws ConstellationException { final MetadataFeeder metadataFeeder = new MetadataFeeder(metadata); final Document doc = new Document(); doc.add(new IntField("dataId",dataId, Field.Store.YES)); addDocument(metadataFeeder,doc); } /** * Adding metadata for given data Id into Lucene index. * * @param metadata given metadata object to index. * @param datasetId dataset Id. * @throws ConstellationException */ @Override public void addMetadataToIndexForDataset(final DefaultMetadata metadata,final Integer datasetId) throws ConstellationException { final MetadataFeeder metadataFeeder = new MetadataFeeder(metadata); final Document doc = new Document(); doc.add(new IntField("datasetId", datasetId, Field.Store.YES)); addDocument(metadataFeeder, doc); } /** * Remove document dataset from index for given dataset Id * @param datasetId given dataset Id. * @throws ConstellationException * * @FIXME delete is not working, the document still present in index after calling deleteDocuments. */ @Override public void removeDatasetMetadataFromIndex(final Integer datasetId) throws ConstellationException { try { indexWriter.deleteDocuments(NumericRangeQuery.newIntRange("datasetId", datasetId, datasetId, true, true)); indexWriter.commit(); }catch(Exception ex){ throw new ConstellationException(ex); } } /** * Remove document data from index for given data id * @param dataId given data Id. * @throws ConstellationException * * * @FIXME delete is not working, the document still present in index after calling deleteDocuments. */ @Override public void removeDataMetadataFromIndex(final Integer dataId) throws ConstellationException { try { indexWriter.deleteDocuments(NumericRangeQuery.newIntRange("dataId", dataId, dataId, true, true)); indexWriter.commit(); }catch(IOException ex){ throw new ConstellationException(ex); } } private void addDocument(final MetadataFeeder metadataFeeder,final Document doc) throws ConstellationException { try { final String keywords = StringUtils.join(metadataFeeder.getKeywords(), " "); if (keywords!=null && keywords.length()>0) { doc.add(new TextField("keywords", keywords, Field.Store.NO)); } final String title = metadataFeeder.getTitle(); if (title != null && title.length()>0){ doc.add(new TextField("title", title, Field.Store.NO)); } final String abstractField = metadataFeeder.getAbstract(); if (abstractField != null && abstractField.length()>0) { doc.add(new TextField("abstract", abstractField, Field.Store.NO)); } final List<String> topicCategories = metadataFeeder.getAllTopicCategory(); if (topicCategories != null && topicCategories.size()>0) { doc.add(new TextField("topic", StringUtils.join(topicCategories, ' '), Field.Store.NO)); } final List<String> sequenceIdentifiers = metadataFeeder.getAllSequenceIdentifier(); if (sequenceIdentifiers != null && sequenceIdentifiers.size()>0){ doc.add(new TextField("data",StringUtils.join(sequenceIdentifiers, ' '),Field.Store.NO)); } final String processingLevel = metadataFeeder.getProcessingLevel(); if (processingLevel != null && processingLevel.length()>0){ doc.add(new TextField("level",processingLevel,Field.Store.NO)); } final List<String> geographicIdentifiers = metadataFeeder.getAllGeographicIdentifier(); if (geographicIdentifiers != null && geographicIdentifiers.size()>0){ doc.add(new TextField("area",StringUtils.join(sequenceIdentifiers, ' '),Field.Store.NO)); } indexWriter.addDocument(doc); indexWriter.commit(); } catch (IOException e) { throw new ConstellationException(e); } } /** * Returns {@code Set} of integer id of documents that matches lucene query. * the id of document is under the property dataId or datasetId. * * @param queryString the lucene query * @param attributeId the attribute targeted for document to return, possible values are dataId or datasetId. * @return {@code Set} of id of dataset if attributeId is 'dataset', and of data if attributeId is 'dataId'. * @throws ParseException * @throws IOException */ @Override public Set<Integer> searchOnMetadata(final String queryString, final String attributeId) throws ParseException, IOException { final Set<Integer> result = new HashSet<>(); initIndexSearcher(); final TopScoreDocCollector collector = TopScoreDocCollector.create(5, true); final ClassicAnalyzer analyzer = new ClassicAnalyzer(Version.LUCENE_4_9); final MultiFieldQueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_4_9, new String[]{"title", "abstract", "keywords", "topic", "data", "level", "area" }, analyzer); queryParser.setDefaultOperator(QueryParser.Operator.OR); final Query q = queryParser.parse(queryString); indexSearcher.search(q, collector); final ScoreDoc[] hits = collector.topDocs().scoreDocs; for (final ScoreDoc scoreDoc : hits){ final Document doc = indexSearcher.doc(scoreDoc.doc); final String value = doc.get(attributeId); if(value !=null){ final Integer dataId = Integer.valueOf(doc.get(attributeId)); result.add(dataId); } } return result; } }