/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search licenses this
* file to you 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.elasticsearch.common.lucene.uid;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PayloadAttribute;
import org.apache.lucene.document.AbstractField;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.*;
import org.elasticsearch.common.Numbers;
import org.elasticsearch.common.lucene.client.Lucene;
import java.io.IOException;
import java.io.Reader;
/**
*
*/
public class UidField extends AbstractField {
public static class DocIdAndVersion {
public final int docId;
public final int docStart;
public final long version;
public final IndexReader reader;
public DocIdAndVersion(int docId, long version, IndexReader reader, int docStart) {
this.docId = docId;
this.version = version;
this.reader = reader;
this.docStart = docStart;
}
}
// this works fine for nested docs since they don't have the payload which has the version
// so we iterate till we find the one with the payload
public static DocIdAndVersion loadDocIdAndVersion(IndexReader subReader, int docStart, Term term) {
int docId = Lucene.NO_DOC;
TermPositions uid = null;
try {
uid = subReader.termPositions(term);
if (!uid.next()) {
return null; // no doc
}
// Note, only master docs uid have version payload, so we can use that info to not
// take them into account
do {
docId = uid.doc();
uid.nextPosition();
if (!uid.isPayloadAvailable()) {
continue;
}
if (uid.getPayloadLength() < 8) {
continue;
}
byte[] payload = uid.getPayload(new byte[8], 0);
return new DocIdAndVersion(docId, Numbers.bytesToLong(payload), subReader, docStart);
} while (uid.next());
return new DocIdAndVersion(docId, -2, subReader, docStart);
} catch (Exception e) {
return new DocIdAndVersion(docId, -2, subReader, docStart);
} finally {
if (uid != null) {
try {
uid.close();
} catch (IOException e) {
// nothing to do here...
}
}
}
}
/**
* Load the version for the uid from the reader, returning -1 if no doc exists, or -2 if
* no version is available (for backward comp.)
*/
public static long loadVersion(IndexReader reader, Term term) {
TermPositions uid = null;
try {
uid = reader.termPositions(term);
if (!uid.next()) {
return -1;
}
// Note, only master docs uid have version payload, so we can use that info to not
// take them into account
do {
uid.nextPosition();
if (!uid.isPayloadAvailable()) {
continue;
}
if (uid.getPayloadLength() < 8) {
continue;
}
byte[] payload = uid.getPayload(new byte[8], 0);
return Numbers.bytesToLong(payload);
} while (uid.next());
return -2;
} catch (Exception e) {
return -2;
} finally {
if (uid != null) {
try {
uid.close();
} catch (IOException e) {
// nothing to do here...
}
}
}
}
private String uid;
private long version;
private final UidPayloadTokenStream tokenStream;
public UidField(String name, String uid, long version) {
super(name, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.NO);
this.uid = uid;
this.version = version;
this.indexOptions = FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS;
this.tokenStream = new UidPayloadTokenStream(this);
}
@Override
public void setIndexOptions(FieldInfo.IndexOptions indexOptions) {
// never allow to set this, since we want payload!
}
@Override
public void setOmitTermFreqAndPositions(boolean omitTermFreqAndPositions) {
// never allow to set this, since we want payload!
}
public String uid() {
return this.uid;
}
public void setUid(String uid) {
this.uid = uid;
}
@Override
public String stringValue() {
return uid;
}
@Override
public Reader readerValue() {
return null;
}
public long version() {
return this.version;
}
public void version(long version) {
this.version = version;
}
@Override
public TokenStream tokenStreamValue() {
return tokenStream;
}
public static final class UidPayloadTokenStream extends TokenStream {
private final PayloadAttribute payloadAttribute = addAttribute(PayloadAttribute.class);
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private final UidField field;
private boolean added = false;
public UidPayloadTokenStream(UidField field) {
this.field = field;
}
@Override
public void reset() throws IOException {
added = false;
}
@Override
public final boolean incrementToken() throws IOException {
if (added) {
return false;
}
termAtt.setLength(0);
termAtt.append(field.uid);
payloadAttribute.setPayload(new Payload(Numbers.longToBytes(field.version())));
added = true;
return true;
}
}
}