org.apache.solr.update.processor.UpdateRequestProcessorChain Maven / Gradle / Ivy
Show all versions of solr-core Show documentation
/*
* 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.update.processor;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.PluginBag;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.pkg.PackagePluginHolder;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.update.processor.UpdateRequestProcessorChain.LazyUpdateProcessorFactoryHolder.LazyUpdateRequestProcessorFactory;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages a chain of UpdateRequestProcessorFactories.
*
* Chains can be configured via solrconfig.xml using the following syntax...
*
*
* <updateRequestProcessorChain name="key" default="true">
* <processor class="package.Class1" />
* <processor class="package.Class2" >
* <str name="someInitParam1">value</str>
* <int name="someInitParam2">42</int>
* </processor>
* <processor class="solr.LogUpdateProcessorFactory" >
* <int name="maxNumToLog">100</int>
* </processor>
* <processor class="solr.RunUpdateProcessorFactory" />
* </updateRequestProcessorChain>
*
*
* Multiple Chains can be defined, each with a distinct name. The name of a chain used to handle
* an update request may be specified using the request param update.chain
. If no chain
* is explicitly selected by name, then Solr will attempt to determine a default chain:
*
*
* - A single configured chain may explicitly be declared with
default="true"
(see
* example above)
* - If no chain is explicitly declared as the default, Solr will look for any chain that does
* not have a name, and treat it as the default
*
- As a last resort, Solr will create an implicit default chain consisting of:
*
* - {@link LogUpdateProcessorFactory}
*
- {@link DistributedUpdateProcessorFactory}
*
- {@link RunUpdateProcessorFactory}
*
*
*
* Almost all processor chains should end with an instance of RunUpdateProcessorFactory
*
unless the user is explicitly executing the update commands in an alternative custom
* UpdateRequestProcessorFactory
. If a chain includes RunUpdateProcessorFactory
*
but does not include a DistributingUpdateProcessorFactory
, it will be added
* automatically by {@link #init init()}.
*
* @see UpdateRequestProcessorFactory
* @see #init
* @see #createProcessor
* @since solr 1.3
*/
public final class UpdateRequestProcessorChain implements PluginInfoInitialized {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private List chain;
private final SolrCore solrCore;
public UpdateRequestProcessorChain(SolrCore solrCore) {
this.solrCore = solrCore;
}
/**
* Initializes the chain using the factories specified by the PluginInfo
. if the
* chain includes the RunUpdateProcessorFactory
, but does not include an
* implementation of the DistributingUpdateProcessorFactory
interface, then an
* instance of DistributedUpdateProcessorFactory
will be injected immediately prior
* to the RunUpdateProcessorFactory
.
*
* @see DistributingUpdateProcessorFactory
* @see RunUpdateProcessorFactory
* @see DistributedUpdateProcessorFactory
*/
@Override
public void init(PluginInfo info) {
final String infomsg =
"updateRequestProcessorChain \""
+ (null != info.name ? info.name : "")
+ "\""
+ (info.isDefault() ? " (default)" : "");
log.debug("creating {}", infomsg);
// wrap in an ArrayList so we know we know we can do fast index lookups
// and that add(int,Object) is supported
List list = createProcessors(info);
if (list.isEmpty()) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, infomsg + " require at least one processor");
}
int numDistrib = 0;
int runIndex = -1;
// hi->lo incase multiple run instances, add before first one
// (no idea why someone might use multiple run instances, but just in case)
for (int i = list.size() - 1; 0 <= i; i--) {
UpdateRequestProcessorFactory factory = list.get(i);
if (factory instanceof DistributingUpdateProcessorFactory) {
numDistrib++;
}
if (factory instanceof RunUpdateProcessorFactory) {
runIndex = i;
}
}
if (1 < numDistrib) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
infomsg
+ " may not contain more then one "
+ "instance of DistributingUpdateProcessorFactory");
}
if (0 <= runIndex && 0 == numDistrib) {
// by default, add distrib processor immediately before run
DistributedUpdateProcessorFactory distrib = new DistributedUpdateProcessorFactory();
distrib.init(new NamedList<>());
list.add(runIndex, distrib);
log.debug("inserting DistributedUpdateProcessorFactory into {}", infomsg);
}
chain = list;
ProcessorInfo processorInfo = new ProcessorInfo(new MapSolrParams(info.attributes));
if (processorInfo.isEmpty()) return;
UpdateRequestProcessorChain newChain = constructChain(this, processorInfo, solrCore);
chain = newChain.chain;
}
private List createProcessors(PluginInfo info) {
List processors = info.getChildren("processor");
return processors.stream()
.map(
it -> {
if (it.pkgName == null) {
return solrCore.createInitInstance(
it,
UpdateRequestProcessorFactory.class,
UpdateRequestProcessorFactory.class.getSimpleName(),
null);
} else {
return new LazyUpdateRequestProcessorFactory(
new PackagePluginHolder<>(
it,
solrCore,
SolrConfig.classVsSolrPluginInfo.get(
UpdateRequestProcessorFactory.class.getName())));
}
})
.collect(Collectors.toList());
}
/**
* Creates a chain backed directly by the specified list. Modifications to the array will affect
* future calls to createProcessor
*/
public UpdateRequestProcessorChain(List chain, SolrCore solrCore) {
this.chain = chain;
this.solrCore = solrCore;
}
/**
* Uses the factories in this chain to creates a new UpdateRequestProcessor
instance
* specific for this request. If the DISTRIB_UPDATE_PARAM
is present in the request
* and is non-blank, then any factory in this chain prior to the instance of
* {@link DistributingUpdateProcessorFactory}
will be skipped, except for the log update
* processor factory.
*
* @see UpdateRequestProcessorFactory#getInstance
* @see DistributingUpdateProcessorFactory#DISTRIB_UPDATE_PARAM
*/
public UpdateRequestProcessor createProcessor(SolrQueryRequest req, SolrQueryResponse rsp) {
final String distribPhase =
req.getParams().get(DistributingUpdateProcessorFactory.DISTRIB_UPDATE_PARAM);
final boolean skipToDistrib = distribPhase != null;
return createProcessor(req, rsp, skipToDistrib, null);
}
/**
* @lucene.internal
*/
public UpdateRequestProcessor createProcessor(
SolrQueryRequest req,
SolrQueryResponse rsp,
boolean skipToDistrib,
UpdateRequestProcessor last) {
boolean afterDistrib = true; // we iterate backwards, so true to start
for (int i = chain.size() - 1; i >= 0; i--) {
UpdateRequestProcessorFactory factory = chain.get(i);
if (skipToDistrib) {
if (afterDistrib) {
if (factory instanceof DistributingUpdateProcessorFactory) {
afterDistrib = false;
}
} else if (!(factory instanceof UpdateRequestProcessorFactory.RunAlways)) {
// skip anything that doesn't have the marker interface
continue;
}
}
// create a new URP with current "last" following it; then replace "last" with this new URP
last = factory.getInstance(req, rsp, last);
}
return last;
}
/**
* Returns the underlying array of factories used in this chain. Modifications to the array will
* affect future calls to createProcessor
*/
public List getProcessors() {
return chain;
}
public static UpdateRequestProcessorChain constructChain(
UpdateRequestProcessorChain defaultUrp, ProcessorInfo processorInfo, SolrCore core) {
List urps = new ArrayList<>(defaultUrp.chain);
List p = getReqProcessors(processorInfo.processor, core);
List post = getReqProcessors(processorInfo.postProcessor, core);
// processor are tried to be inserted before LogUpdateprocessor+DistributedUpdateProcessor
insertBefore(urps, p, DistributedUpdateProcessorFactory.class, 0);
// port-processor is tried to be inserted before RunUpdateProcessor
insertBefore(urps, post, RunUpdateProcessorFactory.class, urps.size() - 1);
UpdateRequestProcessorChain result = new UpdateRequestProcessorChain(urps, core);
if (log.isDebugEnabled()) {
ArrayList names = new ArrayList<>(urps.size());
for (UpdateRequestProcessorFactory urp : urps) names.add(urp.getClass().getSimpleName());
if (log.isDebugEnabled()) {
log.debug("New dynamic chain constructed : {}", StrUtils.join(names, '>'));
}
}
return result;
}
private static void insertBefore(
List urps,
List newFactories,
Class klas,
int idx) {
if (newFactories.isEmpty()) return;
for (int i = 0; i < urps.size(); i++) {
if (klas.isInstance(urps.get(i))) {
idx = i;
if (klas == DistributedUpdateProcessorFactory.class) {
if (i > 0 && urps.get(i - 1) instanceof LogUpdateProcessorFactory) {
idx = i - 1;
}
}
break;
}
}
for (int i = newFactories.size() - 1; 0 <= i; i--) urps.add(idx, newFactories.get(i));
}
static List getReqProcessors(String processor, SolrCore core) {
if (processor == null) return Collections.emptyList();
List result = new ArrayList<>();
List names = StrUtils.splitSmart(processor, ',');
for (String s : names) {
s = s.trim();
if (s.isEmpty()) continue;
UpdateRequestProcessorFactory p = null;
PluginBag.PluginHolder holder =
core.getUpdateProcessors().getRegistry().get(s);
if (holder instanceof PackagePluginHolder) {
p = new LazyUpdateRequestProcessorFactory(holder);
} else {
p = core.getUpdateProcessors().get(s);
}
if (p == null) {
Class factoryClass = implicits.get(s);
if (factoryClass != null) {
PluginInfo pluginInfo =
new PluginInfo("updateProcessor", Map.of("name", s, "class", factoryClass.getName()));
UpdateRequestProcessorFactory plugin =
p = core.getUpdateProcessors().createPlugin(pluginInfo).get();
if (plugin instanceof SolrCoreAware) ((SolrCoreAware) plugin).inform(core);
core.getUpdateProcessors().put(s, plugin);
}
if (p == null)
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No such processor " + s);
}
result.add(p);
}
return result;
}
public static class ProcessorInfo {
public final String processor, postProcessor;
public ProcessorInfo(SolrParams params) {
processor = params.get("processor");
postProcessor = params.get("post-processor");
}
public boolean isEmpty() {
return processor == null && postProcessor == null;
}
@Override
public int hashCode() {
int hash = 0;
if (processor != null) hash += processor.hashCode();
if (postProcessor != null) hash += postProcessor.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ProcessorInfo)) return false;
ProcessorInfo that = (ProcessorInfo) obj;
return Objects.equals(this.processor, that.processor)
&& Objects.equals(this.postProcessor, that.postProcessor);
}
}
public static class LazyUpdateProcessorFactoryHolder
extends PluginBag.PluginHolder {
private volatile UpdateRequestProcessorFactory lazyFactory;
public LazyUpdateProcessorFactoryHolder(
final PluginBag.PluginHolder holder) {
super(holder.getPluginInfo());
lazyFactory = new LazyUpdateRequestProcessorFactory(holder);
}
@Override
public UpdateRequestProcessorFactory get() {
// don't initialize the delegate now. wait for the actual instance creation
return lazyFactory;
}
public static class LazyUpdateRequestProcessorFactory extends UpdateRequestProcessorFactory {
private final PluginBag.PluginHolder holder;
public LazyUpdateRequestProcessorFactory(
PluginBag.PluginHolder holder) {
this.holder = holder;
}
public UpdateRequestProcessorFactory getDelegate() {
return holder.get();
}
@Override
public UpdateRequestProcessor getInstance(
SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor next) {
return holder.get().getInstance(req, rsp, next);
}
}
}
public static final Map> implicits =
Map.of(
TemplateUpdateProcessorFactory.NAME, TemplateUpdateProcessorFactory.class,
AtomicUpdateProcessorFactory.NAME, AtomicUpdateProcessorFactory.class,
UUIDUpdateProcessorFactory.NAME, UUIDUpdateProcessorFactory.class);
}