| /* |
| * 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><str></code>, |
| * the <code>source</code> fields can be configured as either: |
| * </p> |
| * <ul> |
| * <li>One or more <code><str></code></li> |
| * <li>An <code><arr></code> of <code><str></code></li> |
| * <li>A <code><lst></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"> |
| * <updateRequestProcessorChain name="multiple-clones"> |
| * <processor class="solr.CloneFieldUpdateProcessorFactory"> |
| * <str name="source">category</str> |
| * <str name="dest">category_s</str> |
| * </processor> |
| * <processor class="solr.CloneFieldUpdateProcessorFactory"> |
| * <arr name="source"> |
| * <str>authors</str> |
| * <str>editors</str> |
| * </arr> |
| * <str name="dest">contributors</str> |
| * </processor> |
| * <processor class="solr.CloneFieldUpdateProcessorFactory"> |
| * <lst name="source"> |
| * <str name="fieldRegex">.*_price</str> |
| * <lst name="exclude"> |
| * <str name="fieldName">list_price</str> |
| * </lst> |
| * </lst> |
| * <str name="dest">all_prices</str> |
| * </processor> |
| * </updateRequestProcessorChain> |
| * </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<SelectorParams>(); |
| |
| 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<String> |
| (FieldMutatingUpdateProcessorFactory.oneOrMany(args, "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.fieldName, |
| srcInclusions.typeName, |
| srcInclusions.typeClass, |
| srcInclusions.fieldRegex, |
| FieldMutatingUpdateProcessor.SELECT_NO_FIELDS); |
| |
| for (SelectorParams exc : srcExclusions) { |
| srcSelector = FieldMutatingUpdateProcessor.wrap |
| (srcSelector, |
| FieldMutatingUpdateProcessor.createFieldNameSelector |
| (core.getResourceLoader(), |
| core, |
| exc.fieldName, |
| exc.typeName, |
| exc.typeClass, |
| exc.fieldRegex, |
| 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); |
| } |
| |
| } |