org.modeshape.jcr.index.lucene.LuceneIndex.java Source code

Java tutorial

Introduction

Here is the source code for org.modeshape.jcr.index.lucene.LuceneIndex.java

Source

/*
 * ModeShape (http://www.modeshape.org)
 *
 * 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.modeshape.jcr.index.lucene;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.Binary;
import javax.jcr.RepositoryException;
import javax.jcr.query.qom.Constraint;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LegacyDoubleField;
import org.apache.lucene.document.LegacyIntField;
import org.apache.lucene.document.LegacyLongField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.api.value.DateTime;
import org.modeshape.jcr.index.lucene.query.LuceneQueryFactory;
import org.modeshape.jcr.spi.index.IndexConstraints;
import org.modeshape.jcr.spi.index.provider.ProvidedIndex;
import org.modeshape.jcr.value.PropertyType;
import org.modeshape.jcr.value.StringFactory;

/**
 * Bases class for indexes stored in Lucene
 * 
 * @author Horia Chiorean (hchiorea@redhat.com)
 * @since 4.5
 */
@ThreadSafe
@Immutable
@SuppressWarnings("deprecation")
public abstract class LuceneIndex implements ProvidedIndex<Object> {

    protected final Logger logger = Logger.getLogger(getClass());
    protected final String name;
    protected final ExecutionContext context;
    protected final IndexWriter writer;
    protected final Map<String, PropertyType> propertyTypesByName;
    protected final LuceneConfig config;
    protected final StringFactory stringFactory;
    protected final Searcher searcher;

    protected LuceneIndex(String name, String workspaceName, LuceneConfig config,
            Map<String, PropertyType> propertyTypesByName, ExecutionContext context) {
        assert !propertyTypesByName.isEmpty();
        this.propertyTypesByName = propertyTypesByName;
        this.name = name;
        this.context = context;
        this.stringFactory = context.getValueFactories().getStringFactory();
        this.config = config;
        this.writer = config.newWriter(workspaceName, name);
        this.searcher = new Searcher(config, writer, name);
    }

    @Override
    public void add(String nodeKey, String propertyName, Object value) {
        add(nodeKey, propertyName, new Object[] { value });
    }

    @Override
    public void remove(String nodeKey) {
        CheckArg.isNotNull(nodeKey, "nodeKey");
        try {
            // mark the nodekey as removed
            writer.deleteDocuments(FieldUtil.idTerm(nodeKey));
        } catch (IOException e) {
            throw new LuceneIndexException(e);
        }
    }

    @Override
    public void remove(String nodeKey, String propertyName, Object value) {
        // we don't really care about the value...
        remove(nodeKey, propertyName);
    }

    @Override
    public void remove(String nodeKey, String propertyName, Object[] values) {
        // we don't really care about the values...
        remove(nodeKey, propertyName);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public long estimateCardinality(List<Constraint> andedConstraints, Map<String, Object> variables) {
        try {
            return searcher.estimateCardinality(andedConstraints, queryFactory(variables));
        } catch (IOException e) {
            throw new LuceneIndexException(e);
        }
    }

    @Override
    public long estimateTotalCount() {
        return writer.numDocs();
    }

    @Override
    public Results filter(IndexConstraints constraints, long cardinalityEstimate) {
        return searcher.filter(constraints, queryFactory(constraints.getVariables()), cardinalityEstimate);
    }

    @Override
    public boolean requiresReindexing() {
        try {
            return !DirectoryReader.indexExists(writer.getDirectory());
        } catch (IOException e) {
            logger.debug(e, "cannot determine if lucene index exists...");
            return false;
        }
    }

    public void commit() {
        if (!writer.hasUncommittedChanges()) {
            return;
        }
        try {
            Map<String, String> oldData = writer.getCommitData();
            Map<String, String> newData = new HashMap<>(oldData);
            preCommit(newData);
            writer.setCommitData(newData);
            writer.commit();
            postCommit();
        } catch (IOException e) {
            throw new LuceneIndexException("Cannot commit index writer", e);
        }
    }

    protected void postCommit() {
        //nothing by default     
    }

    protected void preCommit(Map<String, String> commitData) {
        commitData.put(LuceneConfig.LAST_SUCCESSFUL_COMMIT_TIME, String.valueOf(System.currentTimeMillis()));
    }

    public void shutdown(boolean destroyed) {
        if (destroyed) {
            clearAllData();
        }
        try {
            searcher.close();
            writer.close();
        } catch (IOException e) {
            throw new LuceneIndexException("Cannot shutdown lucene index", e);
        }
    }

    public void clearAllData() {
        try {
            writer.deleteAll();
            writer.commit();
        } catch (IOException e) {
            throw new LuceneIndexException("Cannot remove all documents from the index");
        }
    }

    protected void addProperty(String nodeKey, Document document, String property, Object... values) {
        if (values != null && values.length > 0) {
            // add the new fields for the given values to the document
            List<Field> fields = valuesToFields(property, values);
            for (Field field : fields) {
                document.add(field);
            }
        }
        // always add the ID (which in the case of an update is removed first)
        document.add(FieldUtil.idField(nodeKey));
    }

    protected abstract void remove(final String nodeKey, final String propertyName);

    protected abstract LuceneQueryFactory queryFactory(Map<String, Object> variables);

    protected List<Field> valuesToFields(String propertyName, Object... values) {
        assert values.length > 0;
        PropertyType type = propertyTypesByName.get(propertyName);
        assert type != null;
        List<Field> fields = new ArrayList<>();
        for (Object value : values) {
            switch (type) {
            case NAME:
            case PATH:
            case REFERENCE:
            case SIMPLEREFERENCE:
            case WEAKREFERENCE:
            case URI:
            case STRING: {
                addStringField(propertyName, stringFactory.create(value), fields);
                break;
            }
            case BOOLEAN: {
                addBooleanField(propertyName, (Boolean) value, fields);
                break;
            }
            case BINARY: {
                // don't cast to anything, because depending on the index type this may be a string or a binary 
                addBinaryField(propertyName, value, fields);
                break;
            }
            case DATE: {
                addDateField(propertyName, ((DateTime) value), fields);
                break;
            }
            case DECIMAL: {
                addDecimalField(propertyName, (BigDecimal) value, fields);
                break;
            }
            case DOUBLE: {
                addDoubleField(propertyName, (Double) value, fields);
                break;
            }
            case LONG: {
                addLongField(propertyName, (Long) value, fields);
                break;
            }
            default:
                throw new LuceneIndexException("Unsupported property type: " + type);
            }
        }
        return fields;
    }

    protected void addStringField(String propertyName, String value, List<Field> fields) {
        fields.add(new StringField(propertyName, value, Field.Store.YES));
        fields.add(new LegacyLongField(FieldUtil.lengthField(propertyName), value.length(), Field.Store.YES));
    }

    protected void addBooleanField(String propertyName, Boolean value, List<Field> fields) {
        String valueString = stringFactory.create(value);
        int intValue = value ? 1 : 0;
        fields.add(new LegacyIntField(propertyName, intValue, Field.Store.YES));
        // add the length
        fields.add(new LegacyLongField(FieldUtil.lengthField(propertyName), valueString.length(), Field.Store.YES));
    }

    protected void addDateField(String propertyName, DateTime value, List<Field> fields) {
        // dates are stored as millis
        fields.add(new LegacyLongField(propertyName, value.getMilliseconds(), Field.Store.YES));
        // add the length
        String valueString = stringFactory.create(value);
        fields.add(new LegacyLongField(FieldUtil.lengthField(propertyName), valueString.length(), Field.Store.YES));
    }

    protected void addBinaryField(String propertyName, Object value, List<Field> fields) {
        // only the length is indexed for binary values by default....
        try {
            Binary binary = (Binary) value;
            fields.add(new LegacyLongField(FieldUtil.lengthField(propertyName), binary.getSize(), Field.Store.YES));
        } catch (RepositoryException e) {
            throw new LuceneIndexException(e);
        }
    }

    protected void addDecimalField(String propertyName, BigDecimal value, List<Field> fields) {
        // big decimals are stored as string, because Lucene doesn't support these natively...
        String stringValue = FieldUtil.decimalToString(value);
        fields.add(new StringField(propertyName, stringValue, Field.Store.YES));
        // add the length using the JCR string format
        fields.add(new LegacyLongField(FieldUtil.lengthField(propertyName), stringFactory.create(value).length(),
                Field.Store.YES));
    }

    protected void addDoubleField(String propertyName, Double value, List<Field> fields) {
        fields.add(new LegacyDoubleField(propertyName, value, Field.Store.YES));
        // add the length
        String valueString = stringFactory.create(value);
        fields.add(new LegacyLongField(FieldUtil.lengthField(propertyName), valueString.length(), Field.Store.YES));
    }

    protected void addLongField(String propertyName, Long value, List<Field> fields) {
        fields.add(new LegacyLongField(propertyName, value.longValue(), Field.Store.YES));
        // add the length
        String valueString = stringFactory.create(value);
        fields.add(new LegacyLongField(FieldUtil.lengthField(propertyName), valueString.length(), Field.Store.YES));
    }
}