PageRenderTime 48ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/solr/core/src/java/org/apache/solr/update/processor/DocBasedVersionConstraintsProcessorFactory.java

http://github.com/apache/lucene-solr
Java | 239 lines | 138 code | 20 blank | 81 comment | 34 complexity | dfca9734433928f90c7975f485d12a2d MD5 | raw file
Possible License(s): LGPL-2.1, CPL-1.0, MPL-2.0-no-copyleft-exception, JSON, Apache-2.0, AGPL-1.0, GPL-2.0, GPL-3.0, MIT, BSD-3-Clause
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.solr.update.processor;
  18. import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
  19. import java.lang.invoke.MethodHandles;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.Set;
  23. import java.util.stream.Collectors;
  24. import org.apache.solr.common.SolrException;
  25. import org.apache.solr.common.util.NamedList;
  26. import org.apache.solr.common.util.StrUtils;
  27. import org.apache.solr.core.SolrCore;
  28. import org.apache.solr.request.SolrQueryRequest;
  29. import org.apache.solr.response.SolrQueryResponse;
  30. import org.apache.solr.schema.IndexSchema;
  31. import org.apache.solr.schema.SchemaField;
  32. import org.apache.solr.util.plugin.SolrCoreAware;
  33. import org.slf4j.Logger;
  34. import org.slf4j.LoggerFactory;
  35. /**
  36. * <p>
  37. * This Factory generates an UpdateProcessor that helps to enforce Version
  38. * constraints on documents based on per-document version numbers using a configured
  39. * <code>versionField</code>, a comma-delimited list of fields to check for version
  40. * numbers. It should be configured on the "default"
  41. * update processor somewhere before the DistributedUpdateProcessorFactory.
  42. * As an example, see the solrconfig.xml that the tests use:
  43. * solr/core/src/test-files/solr/collection1/conf/solrconfig-externalversionconstraint.xml
  44. * </p>
  45. * <p>
  46. * When documents are added through this processor, if a document with the same
  47. * unique key already exists in the collection, then the values within the fields
  48. * as specified by the comma-delimited <code>versionField</code> property are checked,
  49. * and if in the <i>existing</i> document the values for all fields are not less than the
  50. * field values in the <i>new</i> document, then the new document is rejected with a
  51. * 409 Version Conflict error.
  52. * </p>
  53. * <p>
  54. * In addition to the mandatory <code>versionField</code> init param, two additional
  55. * optional init params affect the behavior of this factory:
  56. * </p>
  57. * <ul>
  58. * <li><code>deleteVersionParam</code> - This string parameter controls whether this
  59. * processor will intercept and inspect Delete By Id commands in addition to adding
  60. * documents. If specified, then the value will specify the name(s) of the request
  61. * parameter(s) which becomes mandatory for all Delete By Id commands. Like
  62. * <code>versionField</code>, <code>deleteVersionParam</code> is comma-delimited.
  63. * For each of the params given, it specifies the document version associated with
  64. * the delete, where the index matches <code>versionField</code>. For example, if
  65. * <code>versionField</code> was set to 'a,b' and <code>deleteVersionParam</code>
  66. * was set to 'p1,p2', p1 should give the version for field 'a' and p2 should give
  67. * the version for field 'b'. If the versions specified using these params are not
  68. * greater then the value in the <code>versionField</code> for any existing document,
  69. * then the delete will fail with a 409 Version Conflict error. When using this
  70. * param, Any Delete By Id command with a high enough document version number to
  71. * succeed will be internally converted into an Add Document command that replaces
  72. * the existing document with a new one which is empty except for the Unique Key
  73. * and fields corresponding to the fields listed in <code>versionField</code>
  74. * to keeping a record of the deleted version so future Add Document commands will
  75. * fail if their "new" version is not high enough.</li>
  76. *
  77. * <li><code>ignoreOldUpdates</code> - This boolean parameter defaults to
  78. * <code>false</code>, but if set to <code>true</code> causes any update with a
  79. * document version that is not great enough to be silently ignored (and return
  80. * a status 200 to the client) instead of generating a 409 Version Conflict error.
  81. * </li>
  82. *
  83. * <li><code>supportMissingVersionOnOldDocs</code> - This boolean parameter defaults to
  84. * <code>false</code>, but if set to <code>true</code> allows any documents written *before*
  85. * this feature is enabled and which are missing the versionField to be overwritten.
  86. * </li>
  87. * <li><code>tombstoneConfig</code> - a list of field names to values to add to the
  88. * created tombstone document. In general is not a good idea to populate tombsone documents
  89. * with anything other than the minimum required fields so that it doean't match queries</li>
  90. * </ul>
  91. * @since 4.6.0
  92. */
  93. public class DocBasedVersionConstraintsProcessorFactory extends UpdateRequestProcessorFactory implements SolrCoreAware, UpdateRequestProcessorFactory.RunAlways {
  94. private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  95. private boolean ignoreOldUpdates = false;
  96. private List<String> versionFields = null;
  97. private List<String> deleteVersionParamNames = Collections.emptyList();
  98. private boolean useFieldCache;
  99. private boolean supportMissingVersionOnOldDocs = false;
  100. private NamedList<Object> tombstoneConfig;
  101. @SuppressWarnings("unchecked")
  102. @Override
  103. public void init( NamedList args ) {
  104. Object tmp = args.remove("versionField");
  105. if (null == tmp) {
  106. throw new SolrException(SERVER_ERROR,
  107. "'versionField' must be configured");
  108. }
  109. if (! (tmp instanceof String) ) {
  110. throw new SolrException(SERVER_ERROR,
  111. "'versionField' must be configured as a <str>");
  112. }
  113. versionFields = StrUtils.splitSmart((String)tmp, ',');
  114. // optional
  115. tmp = args.remove("deleteVersionParam");
  116. if (null != tmp) {
  117. if (! (tmp instanceof String) ) {
  118. throw new SolrException(SERVER_ERROR,
  119. "'deleteVersionParam' must be configured as a <str>");
  120. }
  121. deleteVersionParamNames = StrUtils.splitSmart((String)tmp, ',');
  122. }
  123. if (deleteVersionParamNames.size() > 0 && deleteVersionParamNames.size() != versionFields.size()) {
  124. throw new SolrException(SERVER_ERROR, "The number of 'deleteVersionParam' params " +
  125. "must either be 0 or equal to the number of 'versionField' fields");
  126. }
  127. // optional - defaults to false
  128. tmp = args.remove("ignoreOldUpdates");
  129. if (null != tmp) {
  130. if (! (tmp instanceof Boolean) ) {
  131. throw new SolrException(SERVER_ERROR,
  132. "'ignoreOldUpdates' must be configured as a <bool>");
  133. }
  134. ignoreOldUpdates = (Boolean) tmp;
  135. }
  136. // optional - defaults to false
  137. tmp = args.remove("supportMissingVersionOnOldDocs");
  138. if (null != tmp) {
  139. if (! (tmp instanceof Boolean) ) {
  140. throw new SolrException(SERVER_ERROR,
  141. "'supportMissingVersionOnOldDocs' must be configured as a <bool>");
  142. }
  143. supportMissingVersionOnOldDocs = ((Boolean)tmp).booleanValue();
  144. }
  145. tmp = args.remove("tombstoneConfig");
  146. if (null != tmp) {
  147. if (! (tmp instanceof NamedList) ) {
  148. throw new SolrException(SERVER_ERROR,
  149. "'tombstoneConfig' must be configured as a <lst>.");
  150. }
  151. tombstoneConfig = (NamedList<Object>)tmp;
  152. }
  153. super.init(args);
  154. }
  155. @Override
  156. public UpdateRequestProcessor getInstance(SolrQueryRequest req,
  157. SolrQueryResponse rsp,
  158. UpdateRequestProcessor next ) {
  159. return new DocBasedVersionConstraintsProcessor(versionFields,
  160. ignoreOldUpdates,
  161. deleteVersionParamNames,
  162. supportMissingVersionOnOldDocs,
  163. useFieldCache,
  164. tombstoneConfig,
  165. req, next);
  166. }
  167. @Override
  168. public void inform(SolrCore core) {
  169. if (core.getUpdateHandler().getUpdateLog() == null) {
  170. throw new SolrException(SERVER_ERROR,
  171. "updateLog must be enabled.");
  172. }
  173. if (core.getLatestSchema().getUniqueKeyField() == null) {
  174. throw new SolrException(SERVER_ERROR,
  175. "schema must have uniqueKey defined.");
  176. }
  177. useFieldCache = true;
  178. for (String versionField : versionFields) {
  179. SchemaField userVersionField = core.getLatestSchema().getField(versionField);
  180. if (userVersionField == null || !userVersionField.stored() || userVersionField.multiValued()) {
  181. throw new SolrException(SERVER_ERROR,
  182. "field " + versionField + " must be defined in schema, be stored, and be single valued.");
  183. }
  184. if (useFieldCache) {
  185. try {
  186. userVersionField.getType().getValueSource(userVersionField, null);
  187. } catch (Exception e) {
  188. useFieldCache = false;
  189. log.warn("Can't use fieldcache/valuesource: {}", e.getMessage());
  190. }
  191. }
  192. }
  193. canCreateTombstoneDocument(core.getLatestSchema());
  194. }
  195. /**
  196. * Validates that the schema would allow tombstones to be created by DocBasedVersionConstraintsProcessor by
  197. * checking if the required fields are of known types
  198. */
  199. protected boolean canCreateTombstoneDocument(IndexSchema schema) {
  200. Set<String> requiredFieldNames = schema.getRequiredFields().stream()
  201. .filter(field -> field.getDefaultValue() == null)
  202. .map(field -> field.getName())
  203. .collect(Collectors.toSet());
  204. if (tombstoneConfig != null) {
  205. tombstoneConfig.forEach((k,v) -> requiredFieldNames.remove(k));
  206. }
  207. requiredFieldNames.remove(schema.getUniqueKeyField().getName());
  208. if (versionFields != null) {
  209. versionFields.forEach(field -> requiredFieldNames.remove(field));
  210. }
  211. if (!requiredFieldNames.isEmpty()) {
  212. log.warn("The schema '{}' has required fields that aren't added in the tombstone. This can cause deletes to fail if those aren't being added in some other way. Required Fields={}",
  213. schema.getSchemaName(),
  214. requiredFieldNames);
  215. return false;
  216. }
  217. return true;
  218. }
  219. }