/*
* Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved.
*
* This file is provided 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 com.basho.yokozuna.handler;
import java.io.IOException;
import org.apache.commons.codec.binary.Base64;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.index.AtomicReader;
import org.apache.lucene.index.DocsEnum;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides handler logic to iterate over the entropy data
* stored in the index. This data can be used to build a hash tree to
* detect entropy.
*/
public class EntropyData
extends RequestHandlerBase
implements PluginInfoInitialized {
protected static final Logger log = LoggerFactory.getLogger(EntropyData.class);
static final BytesRef DEFAULT_CONT = null;
static final int DEFAULT_N = 1000;
static final String ENTROPY_DATA_FIELD = "_yz_ed";
// Pass info from solrconfig.xml
public void init(final PluginInfo info) {
init(info.initArgs);
}
@Override
public void handleRequestBody(final SolrQueryRequest req, final SolrQueryResponse rsp)
throws Exception, InstantiationException, IllegalAccessException {
final String contParam = req.getParams().get("continue");
final BytesRef cont = contParam != null ?
decodeCont(contParam) : DEFAULT_CONT;
final int n = req.getParams().getInt("n", DEFAULT_N);
final String partition = req.getParams().get("partition");
if (partition == null) {
throw new Exception("Parameter 'partition' is required");
}
final SolrDocumentList docs = new SolrDocumentList();
// Add docs here and modify object inline in code
rsp.add("response", docs);
try {
final SolrIndexSearcher searcher = req.getSearcher();
final AtomicReader rdr = searcher.getAtomicReader();
BytesRef tmp = null;
final Terms terms = rdr.terms(ENTROPY_DATA_FIELD);
if (terms == null) {
rsp.add("more", false);
return;
}
final TermsEnum te = terms.iterator(null);
if (isContinue(cont)) {
if (log.isDebugEnabled()) {
log.debug("continue from " + cont);
}
final TermsEnum.SeekStatus status = te.seekCeil(cont);
if (status == TermsEnum.SeekStatus.END) {
rsp.add("more", false);
return;
} else if (status == TermsEnum.SeekStatus.FOUND) {
// If this term has already been seen then skip it.
tmp = te.next();
if (endOfItr(tmp)) {
rsp.add("more", false);
return;
}
} else if (status == TermsEnum.SeekStatus.NOT_FOUND) {
tmp = te.next();
}
} else {
tmp = te.next();
}
String text;
String[] vals;
String docPartition;
String vsn;
String riakBType;
String riakBName;
String riakKey;
String hash;
int count = 0;
BytesRef current = null;
final Bits liveDocs = rdr.getLiveDocs();
while(!endOfItr(tmp) && count < n) {
if (isLive(liveDocs, te)) {
current = BytesRef.deepCopyOf(tmp);
text = tmp.utf8ToString();
if (log.isDebugEnabled()) {
log.debug("text: " + text);
}
vals = text.split(" ");
vsn = vals[0];
docPartition = vals[1];
riakBType = vals[2];
riakBName = vals[3];
riakKey = vals[4];
hash = vals[5];
if (partition.equals(docPartition)) {
SolrDocument tmpDoc = new SolrDocument();
tmpDoc.addField("vsn", vsn);
tmpDoc.addField("riak_bucket_type", riakBType);
tmpDoc.addField("riak_bucket_name", riakBName);
tmpDoc.addField("riak_key", riakKey);
tmpDoc.addField("base64_hash", hash);
docs.add(tmpDoc);
count++;
}
}
tmp = te.next();
}
if (count < n) {
rsp.add("more", false);
} else {
rsp.add("more", true);
final String newCont = Base64.encodeBase64URLSafeString(current.bytes);
// The continue context for next req to start where
// this one finished.
rsp.add("continuation", newCont);
}
docs.setNumFound(count);
} catch (final Exception e) {
e.printStackTrace();
}
}
static boolean isLive(final Bits liveDocs, final TermsEnum te) throws IOException {
final DocsEnum de = te.docs(liveDocs, null);
return de.nextDoc() != DocIdSetIterator.NO_MORE_DOCS;
}
static BytesRef decodeCont(final String cont) {
final byte[] bytes = Base64.decodeBase64(cont);
return new BytesRef(bytes);
}
static boolean endOfItr(final BytesRef returnValue) {
return returnValue == null;
}
static boolean isContinue(final BytesRef cont) {
return DEFAULT_CONT != cont;
}
@Override
public String getDescription() {
return "vector clock data iterator";
}
@Override
public String getVersion() {
return "0.0.1";
}
@Override
public String getSource() {
return "TODO: implement getSource";
}
}