All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.handler.BlobHandler Maven / Gradle / Ivy

There is a newer version: 9.6.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.apache.solr.handler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.Hex;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.solr.api.Api;
import org.apache.solr.api.ApiBag;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.FieldType;
import org.apache.solr.search.QParser;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.solr.update.AddUpdateCommand;
import org.apache.solr.update.CommitUpdateCommand;
import org.apache.solr.update.processor.UpdateRequestProcessor;
import org.apache.solr.update.processor.UpdateRequestProcessorChain;
import org.apache.solr.util.SimplePostTool;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Collections.singletonMap;
import static org.apache.solr.common.params.CommonParams.ID;
import static org.apache.solr.common.params.CommonParams.JSON;
import static org.apache.solr.common.params.CommonParams.SORT;
import static org.apache.solr.common.params.CommonParams.VERSION;
import static org.apache.solr.common.util.Utils.makeMap;

public class BlobHandler extends RequestHandlerBase implements PluginInfoInitialized , PermissionNameProvider {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  private static final long DEFAULT_MAX_SIZE = 5 * 1024 * 1024; // 5MB
  private long maxSize = DEFAULT_MAX_SIZE;

  @Override
  public void handleRequestBody(final SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
    String httpMethod = req.getHttpMethod();
    String path = (String) req.getContext().get("path");
    RequestHandlerUtils.setWt(req, JSON);

    List pieces = StrUtils.splitSmart(path, '/');
    String blobName = null;
    if (pieces.size() >= 3) blobName = pieces.get(2);

    if ("POST".equals(httpMethod)) {
      if (blobName == null || blobName.isEmpty()) {
        rsp.add("error", "Name not found");
        return;
      }
      String err = SolrConfigHandler.validateName(blobName);
      if (err != null) {
        log.warn("no blob name");
        rsp.add("error", err);
        return;
      }
      if (req.getContentStreams() == null) {
        log.warn("no content stream");
        rsp.add("error", "No stream");
        return;
      }


      for (ContentStream stream : req.getContentStreams()) {
        ByteBuffer payload;
        try (InputStream is = stream.getStream()) {
          payload = SimplePostTool.inputStreamToByteArray(is, maxSize);
        }
        MessageDigest m = MessageDigest.getInstance("MD5");
        m.update(payload.array(), payload.position(), payload.limit());
        String md5 = new String(Hex.encodeHex(m.digest()));

        int duplicateCount = req.getSearcher().count(new TermQuery(new Term("md5", md5)));
        if (duplicateCount > 0) {
          rsp.add("error", "duplicate entry");
          forward(req, null,
              new MapSolrParams((Map) makeMap(
                  "q", "md5:" + md5,
                  "fl", "id,size,version,timestamp,blobName")),
              rsp);
          log.warn("duplicate entry for blob :" + blobName);
          return;
        }

        TopFieldDocs docs = req.getSearcher().search(new TermQuery(new Term("blobName", blobName)),
            1, new Sort(new SortField("version", SortField.Type.LONG, true)));

        long version = 0;
        if (docs.totalHits.value > 0) {
          Document doc = req.getSearcher().doc(docs.scoreDocs[0].doc);
          Number n = doc.getField("version").numericValue();
          version = n.longValue();
        }
        version++;
        String id = blobName + "/" + version;
        Map doc = makeMap(
            ID, id,
            CommonParams.TYPE, "blob",
            "md5", md5,
            "blobName", blobName,
            VERSION, version,
            "timestamp", new Date(),
            "size", payload.limit(),
            "blob", payload);
        verifyWithRealtimeGet(blobName, version, req, doc);
        log.info(StrUtils.formatString("inserting new blob {0} ,size {1}, md5 {2}", doc.get(ID), String.valueOf(payload.limit()), md5));
        indexMap(req, rsp, doc);
        log.info(" Successfully Added and committed a blob with id {} and size {} ", id, payload.limit());

        break;
      }

    } else {
      int version = -1;
      if (pieces.size() > 3) {
        try {
          version = Integer.parseInt(pieces.get(3));
        } catch (NumberFormatException e) {
          rsp.add("error", "Invalid version" + pieces.get(3));
          return;
        }

      }
      if (ReplicationHandler.FILE_STREAM.equals(req.getParams().get(CommonParams.WT))) {
        if (blobName == null) {
          throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Please send the request in the format /blob//");
        } else {
          String q = "blobName:{0}";
          if (version != -1) q = "id:{0}/{1}";
          QParser qparser = QParser.getParser(StrUtils.formatString(q, blobName, version), req);
          final TopDocs docs = req.getSearcher().search(qparser.parse(), 1, new Sort(new SortField("version", SortField.Type.LONG, true)));
          if (docs.totalHits.value > 0) {
            rsp.add(ReplicationHandler.FILE_STREAM, new SolrCore.RawWriter() {

              @Override
              public void write(OutputStream os) throws IOException {
                Document doc = req.getSearcher().doc(docs.scoreDocs[0].doc);
                IndexableField sf = doc.getField("blob");
                FieldType fieldType = req.getSchema().getField("blob").getType();
                ByteBuffer buf = (ByteBuffer) fieldType.toObject(sf);
                if (buf == null) {
                  //should never happen unless a user wrote this document directly
                  throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Invalid document . No field called blob");
                } else {
                  os.write(buf.array(), 0, buf.limit());
                }
              }
            });

          } else {
            throw new SolrException(SolrException.ErrorCode.NOT_FOUND,
                StrUtils.formatString("Invalid combination of blobName {0} and version {1}", blobName, version));
          }

        }
      } else {
        String q = "*:*";
        if (blobName != null) {
          q = "blobName:{0}";
          if (version != -1) {
            q = "id:{0}/{1}";
          }
        }

        forward(req, null,
            new MapSolrParams((Map) makeMap(
                "q", StrUtils.formatString(q, blobName, version),
                "fl", "id,size,version,timestamp,blobName,md5",
                SORT, "version desc"))
            , rsp);
      }
    }
  }

  private void verifyWithRealtimeGet(String blobName, long version, SolrQueryRequest req, Map doc) {
    for (; ; ) {
      SolrQueryResponse response = new SolrQueryResponse();
      String id = blobName + "/" + version;
      forward(req, "/get", new MapSolrParams(singletonMap(ID, id)), response);
      if (response.getValues().get("doc") == null) {
        //ensure that the version does not exist
        return;
      } else {
        log.info("id {} already exists trying next ", id);
        version++;
        doc.put("version", version);
        id = blobName + "/" + version;
        doc.put(ID, id);
      }
    }

  }

  public static void indexMap(SolrQueryRequest req, SolrQueryResponse rsp, Map doc) throws IOException {
    SolrInputDocument solrDoc = new SolrInputDocument();
    for (Map.Entry e : doc.entrySet()) solrDoc.addField(e.getKey(), e.getValue());
    UpdateRequestProcessorChain processorChain = req.getCore().getUpdateProcessorChain(req.getParams());
    try (UpdateRequestProcessor processor = processorChain.createProcessor(req, rsp)) {
      AddUpdateCommand cmd = new AddUpdateCommand(req);
      cmd.solrDoc = solrDoc;
      log.info("Adding doc: " + doc);
      processor.processAdd(cmd);
      log.info("committing doc: " + doc);
      processor.processCommit(new CommitUpdateCommand(req, false));
      processor.finish();
    }
  }

  @Override
  public SolrRequestHandler getSubHandler(String subPath) {
    if (StrUtils.splitSmart(subPath, '/').size() > 4) return null;
    return this;
  }


//////////////////////// SolrInfoMBeans methods //////////////////////

  @Override
  public String getDescription() {
    return "Load Jars into a system index";
  }


  @Override
  public void init(PluginInfo info) {
    super.init(info.initArgs);
    if (info.initArgs != null) {
      NamedList invariants = (NamedList) info.initArgs.get(PluginInfo.INVARIANTS);
      if (invariants != null) {
        Object o = invariants.get("maxSize");
        if (o != null) {
          maxSize = Long.parseLong(String.valueOf(o));
          maxSize = maxSize * 1024 * 1024;
        }
      }

    }
  }

  // This does not work for the general case of forwarding requests.  It probably currently
  // works OK for real-time get (which is all that BlobHandler uses it for).
  private static void forward(SolrQueryRequest req, String handler ,SolrParams params, SolrQueryResponse rsp){
    LocalSolrQueryRequest r = new LocalSolrQueryRequest(req.getCore(), params);
    SolrRequestInfo.getRequestInfo().addCloseHook( r );  // Close as late as possible...
    req.getCore().getRequestHandler(handler).handleRequest(r, rsp);
  }

  @Override
  public Boolean registerV2() {
    return Boolean.TRUE;
  }

  @Override
  public Collection getApis() {
    return ApiBag.wrapRequestHandlers(this, "core.system.blob", "core.system.blob.upload");
  }

  @Override
  public Name getPermissionName(AuthorizationContext ctx) {
    switch (ctx.getHttpMethod()) {
      case "GET":
        return Name.READ_PERM;
      case "POST":
        return Name.UPDATE_PERM;
      default:
        return null;
    }

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy