blob: 29ad7d29234b82ccc5e3524a4803bdfd40bc2a06 [file] [log] [blame]
/*
* 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.io.IOException;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import org.apache.solr.core.SolrCore;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrException;
import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.update.AddUpdateCommand;
import org.apache.solr.update.processor.FieldMutatingUpdateProcessorFactory.SelectorParams;
import org.apache.solr.update.processor.FieldMutatingUpdateProcessor.FieldNameSelector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Clones the values found in any matching <code>source</code> field into
* the configured <code>dest</code> field.
* <p>
* While the <code>dest</code> field must be a single <code>&lt;str&gt;</code>,
* the <code>source</code> fields can be configured as either:
* </p>
* <ul>
* <li>One or more <code>&lt;str&gt;</code></li>
* <li>An <code>&lt;arr&gt;</code> of <code>&lt;str&gt;</code></li>
* <li>A <code>&lt;lst&gt;</code> containing {@link FieldMutatingUpdateProcessorFactory FieldMutatingUpdateProcessorFactory style selector arguments}</li>
* </ul>
* <p>
* If the <code>dest</code> field already exists in the document, then the
* values from the <code>source</code> fields will be added to it. The
* "boost" value associated with the <code>dest</code> will not be changed,
* and any boost specified on the <code>source</code> fields will be ignored.
* (If the <code>dest</code> field did not exist prior to this processor, the
* newly created <code>dest</code> field will have the default boost of 1.0)
* </p>
* <p>
* In the example below, the <code>category</code> field will be cloned
* into the <code>category_s</code> field, both the <code>authors</code> and
* <code>editors</code> fields will be cloned into the <code>contributors</code>
* field, and any field with a name ending in <code>_price</code> -- except for
* <code>list_price</code> -- will be cloned into the <code>all_prices</code>
* field.
* </p>
* <!-- see solrconfig-update-processors-chains.xml for where this is tested -->
* <pre class="prettyprint">
* &lt;updateRequestProcessorChain name="multiple-clones"&gt;
* &lt;processor class="solr.CloneFieldUpdateProcessorFactory"&gt;
* &lt;str name="source"&gt;category&lt;/str&gt;
* &lt;str name="dest"&gt;category_s&lt;/str&gt;
* &lt;/processor&gt;
* &lt;processor class="solr.CloneFieldUpdateProcessorFactory"&gt;
* &lt;arr name="source"&gt;
* &lt;str&gt;authors&lt;/str&gt;
* &lt;str&gt;editors&lt;/str&gt;
* &lt;/arr&gt;
* &lt;str name="dest"&gt;contributors&lt;/str&gt;
* &lt;/processor&gt;
* &lt;processor class="solr.CloneFieldUpdateProcessorFactory"&gt;
* &lt;lst name="source"&gt;
* &lt;str name="fieldRegex"&gt;.*_price&lt;/str&gt;
* &lt;lst name="exclude"&gt;
* &lt;str name="fieldName"&gt;list_price&lt;/str&gt;
* &lt;/lst&gt;
* &lt;/lst&gt;
* &lt;str name="dest"&gt;all_prices&lt;/str&gt;
* &lt;/processor&gt;
* &lt;/updateRequestProcessorChain&gt;
* </pre>
*/
public class CloneFieldUpdateProcessorFactory
extends UpdateRequestProcessorFactory implements SolrCoreAware {
private final static Logger log = LoggerFactory.getLogger(CloneFieldUpdateProcessorFactory.class);
public static final String SOURCE_PARAM = "source";
public static final String DEST_PARAM = "dest";
private SelectorParams srcInclusions = new SelectorParams();
private Collection<SelectorParams> srcExclusions
= new ArrayList<>();
private FieldNameSelector srcSelector = null;
private String dest = null;
protected final FieldNameSelector getSourceSelector() {
if (null != srcSelector) return srcSelector;
throw new SolrException(SERVER_ERROR, "selector was never initialized, "+
" inform(SolrCore) never called???");
}
@SuppressWarnings("unchecked")
@Override
public void init(NamedList args) {
Object d = args.remove(DEST_PARAM);
if (null == d) {
throw new SolrException
(SERVER_ERROR, "Init param '" + DEST_PARAM + "' must be specified");
} else if (! (d instanceof CharSequence) ) {
throw new SolrException
(SERVER_ERROR, "Init param '" + DEST_PARAM + "' must be a string (ie: 'str')");
}
dest = d.toString();
List<Object> sources = args.getAll(SOURCE_PARAM);
if (0 == sources.size()) {
throw new SolrException
(SERVER_ERROR, "Init param '" + SOURCE_PARAM + "' must be specified");
}
if (1 == sources.size() && sources.get(0) instanceof NamedList) {
// nested set of selector options
NamedList selectorConfig = (NamedList) args.remove(SOURCE_PARAM);
srcInclusions = parseSelectorParams(selectorConfig);
List<Object> excList = selectorConfig.getAll("exclude");
for (Object excObj : excList) {
if (null == excObj) {
throw new SolrException
(SERVER_ERROR, "Init param '" + SOURCE_PARAM +
"' child 'exclude' can not be null");
}
if (! (excObj instanceof NamedList) ) {
throw new SolrException
(SERVER_ERROR, "Init param '" + SOURCE_PARAM +
"' child 'exclude' must be <lst/>");
}
NamedList exc = (NamedList) excObj;
srcExclusions.add(parseSelectorParams(exc));
if (0 < exc.size()) {
throw new SolrException(SERVER_ERROR, "Init param '" + SOURCE_PARAM +
"' has unexpected 'exclude' sub-param(s): '"
+ selectorConfig.getName(0) + "'");
}
// call once per instance
selectorConfig.remove("exclude");
}
if (0 < selectorConfig.size()) {
throw new SolrException(SERVER_ERROR, "Init param '" + SOURCE_PARAM +
"' contains unexpected child param(s): '" +
selectorConfig.getName(0) + "'");
}
} else {
// source better be one or more strings
srcInclusions.fieldName = new HashSet<>(args.removeConfigArgs("source"));
}
if (0 < args.size()) {
throw new SolrException(SERVER_ERROR,
"Unexpected init param(s): '" +
args.getName(0) + "'");
}
super.init(args);
}
@Override
public void inform(final SolrCore core) {
srcSelector =
FieldMutatingUpdateProcessor.createFieldNameSelector
(core.getResourceLoader(), core, srcInclusions, FieldMutatingUpdateProcessor.SELECT_NO_FIELDS);
for (SelectorParams exc : srcExclusions) {
srcSelector = FieldMutatingUpdateProcessor.wrap
(srcSelector,
FieldMutatingUpdateProcessor.createFieldNameSelector
(core.getResourceLoader(), core, exc, FieldMutatingUpdateProcessor.SELECT_NO_FIELDS));
}
}
@Override
public final UpdateRequestProcessor getInstance(SolrQueryRequest req,
SolrQueryResponse rsp,
UpdateRequestProcessor next) {
return new UpdateRequestProcessor(next) {
@Override
public void processAdd(AddUpdateCommand cmd) throws IOException {
final SolrInputDocument doc = cmd.getSolrInputDocument();
// preserve initial values and boost (if any)
SolrInputField destField = doc.containsKey(dest) ?
doc.getField(dest) : new SolrInputField(dest);
boolean modified = false;
for (final String fname : doc.getFieldNames()) {
if (! srcSelector.shouldMutate(fname)) continue;
for (Object val : doc.getFieldValues(fname)) {
// preserve existing dest boost (multiplicitive), ignore src boost
destField.addValue(val, 1.0f);
}
modified=true;
}
if (modified) doc.put(dest, destField);
super.processAdd(cmd);
}
};
}
/** macro */
private static SelectorParams parseSelectorParams(NamedList args) {
return FieldMutatingUpdateProcessorFactory.parseSelectorParams(args);
}
}