blob: f3fa92ce171cab3acef6a146a1f4ad6e2df6ec42 [file] [log] [blame]
package org.apache.solr.schema;
/*
* 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.
*/
import org.apache.commons.io.IOUtils;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.core.Config;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.util.FileUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.xpath.XPath;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
/** Solr-managed schema - non-user-editable, but can be mutable via internal and external REST API requests. */
public final class ManagedIndexSchema extends IndexSchema {
private boolean isMutable = false;
@Override public boolean isMutable() { return isMutable; }
final String managedSchemaResourceName;
int schemaZkVersion;
final Object schemaUpdateLock;
/**
* Constructs a schema using the specified resource name and stream.
*
* @see org.apache.solr.core.SolrResourceLoader#openSchema
* By default, this follows the normal config path directory searching rules.
* @see org.apache.solr.core.SolrResourceLoader#openResource
*/
ManagedIndexSchema(SolrConfig solrConfig, String name, InputSource is, boolean isMutable,
String managedSchemaResourceName, int schemaZkVersion, Object schemaUpdateLock)
throws KeeperException, InterruptedException {
super(solrConfig, name, is);
this.isMutable = isMutable;
this.managedSchemaResourceName = managedSchemaResourceName;
this.schemaZkVersion = schemaZkVersion;
this.schemaUpdateLock = schemaUpdateLock;
}
/** Persist the schema to local storage or to ZooKeeper */
boolean persistManagedSchema(boolean createOnly) {
if (loader instanceof ZkSolrResourceLoader) {
return persistManagedSchemaToZooKeeper(createOnly);
}
// Persist locally
File managedSchemaFile = new File(loader.getConfigDir(), managedSchemaResourceName);
OutputStreamWriter writer = null;
try {
File parentDir = managedSchemaFile.getParentFile();
if ( ! parentDir.isDirectory()) {
if ( ! parentDir.mkdirs()) {
final String msg = "Can't create managed schema directory " + parentDir.getAbsolutePath();
log.error(msg);
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
}
}
final FileOutputStream out = new FileOutputStream(managedSchemaFile);
writer = new OutputStreamWriter(out, "UTF-8");
persist(writer);
log.info("Upgraded to managed schema at " + managedSchemaFile.getPath());
} catch (IOException e) {
final String msg = "Error persisting managed schema " + managedSchemaFile;
log.error(msg, e);
throw new SolrException(ErrorCode.SERVER_ERROR, msg, e);
} finally {
IOUtils.closeQuietly(writer);
try {
FileUtils.sync(managedSchemaFile);
} catch (IOException e) {
final String msg = "Error syncing the managed schema file " + managedSchemaFile;
log.error(msg, e);
}
}
return true;
}
/**
* Persists the managed schema to ZooKeeper using optimistic concurrency.
* <p/>
* If createOnly is true, success is when the schema is created or if it previously existed.
* <p/>
* If createOnly is false, success is when the schema is persisted - this will only happen
* if schemaZkVersion matches the version in ZooKeeper.
*
* @return true on success
*/
boolean persistManagedSchemaToZooKeeper(boolean createOnly) {
final ZkSolrResourceLoader zkLoader = (ZkSolrResourceLoader)loader;
final ZkController zkController = zkLoader.getZkController();
final SolrZkClient zkClient = zkController.getZkClient();
final String managedSchemaPath = zkLoader.getCollectionZkPath() + "/" + managedSchemaResourceName;
boolean success = true;
try {
// Persist the managed schema
StringWriter writer = new StringWriter();
persist(writer);
final byte[] data = writer.toString().getBytes("UTF-8");
if (createOnly) {
try {
zkClient.create(managedSchemaPath, data, CreateMode.PERSISTENT, true);
schemaZkVersion = 0;
log.info("Created and persisted managed schema znode at " + managedSchemaPath);
} catch (KeeperException.NodeExistsException e) {
// This is okay - do nothing and fall through
log.info("Managed schema znode at " + managedSchemaPath + " already exists - no need to create it");
}
} else {
try {
// Assumption: the path exists
Stat stat = zkClient.setData(managedSchemaPath, data, schemaZkVersion, true);
schemaZkVersion = stat.getVersion();
log.info("Persisted managed schema at " + managedSchemaPath);
} catch (KeeperException.BadVersionException e) {
log.info("Failed to persist managed schema at " + managedSchemaPath
+ " - version mismatch");
success = false;
}
}
} catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt(); // Restore the interrupted status
}
final String msg = "Error persisting managed schema at " + managedSchemaPath;
log.error(msg, e);
throw new SolrException(ErrorCode.SERVER_ERROR, msg, e);
}
return success;
}
@Override
public ManagedIndexSchema addField(SchemaField newField) {
return addFields(Arrays.asList(newField));
}
public class FieldExistsException extends SolrException {
public FieldExistsException(ErrorCode code, String msg) {
super(code, msg);
}
}
@Override
public ManagedIndexSchema addFields(Collection<SchemaField> newFields) {
ManagedIndexSchema newSchema = null;
if (isMutable) {
boolean success = false;
while ( ! success) { // optimistic concurrency
// even though fields is volatile, we need to synchronize to avoid two addFields
// happening concurrently (and ending up missing one of them)
synchronized (getSchemaUpdateLock()) {
newSchema = shallowCopy(true);
for (SchemaField newField : newFields) {
if (null != newSchema.getFieldOrNull(newField.getName())) {
String msg = "Field '" + newField.getName() + "' already exists.";
throw new FieldExistsException(ErrorCode.BAD_REQUEST, msg);
}
newSchema.fields.put(newField.getName(), newField);
if (null != newField.getDefaultValue()) {
log.debug(newField.getName() + " contains default value: " + newField.getDefaultValue());
newSchema.fieldsWithDefaultValue.add(newField);
}
if (newField.isRequired()) {
log.debug("{} is required in this schema", newField.getName());
newSchema.requiredFields.add(newField);
}
}
// Run the callbacks on SchemaAware now that everything else is done
for (SchemaAware aware : newSchema.schemaAware) {
aware.inform(newSchema);
}
newSchema.refreshAnalyzers();
success = newSchema.persistManagedSchema(false); // don't just create - update it if it already exists
if (success) {
log.debug("Added field(s): {}", newFields);
}
}
// release the lock between tries to allow the schema reader to update the schema & schemaZkVersion
}
} else {
String msg = "This ManagedIndexSchema is not mutable.";
log.error(msg);
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
}
return newSchema;
}
@Override
public SchemaField newField(String fieldName, String fieldType, Map<String,?> options) {
SchemaField sf;
if (isMutable) {
try {
if (-1 != fieldName.indexOf('*')) {
String msg = "Can't add dynamic field '" + fieldName + "'.";
throw new SolrException(ErrorCode.BAD_REQUEST, msg);
}
SchemaField existingFieldWithTheSameName = getFieldOrNull(fieldName);
if (null != existingFieldWithTheSameName) {
String msg = "Field '" + fieldName + "' already exists.";
throw new SolrException(ErrorCode.BAD_REQUEST, msg);
}
FieldType type = getFieldTypeByName(fieldType);
if (null == type) {
String msg = "Field '" + fieldName + "': Field type '" + fieldType + "' not found.";
log.error(msg);
throw new SolrException(ErrorCode.BAD_REQUEST, msg);
}
sf = SchemaField.create(fieldName, type, options);
} catch (SolrException e) {
throw e;
} catch (Exception e) {
throw new SolrException(ErrorCode.BAD_REQUEST, e);
}
} else {
String msg = "This ManagedIndexSchema is not mutable.";
log.error(msg);
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
}
return sf;
}
/**
* Called from ZkIndexSchemaReader to merge the fields from the serialized managed schema
* on ZooKeeper with the local managed schema.
*
* @param inputSource The serialized content of the managed schema from ZooKeeper
* @param schemaZkVersion The ZK version of the managed schema on ZooKeeper
* @return The new merged schema
*/
ManagedIndexSchema reloadFields(InputSource inputSource, int schemaZkVersion) {
ManagedIndexSchema newSchema;
try {
newSchema = shallowCopy(false);
Config schemaConf = new Config(loader, SCHEMA, inputSource, SLASH+SCHEMA+SLASH);
Document document = schemaConf.getDocument();
final XPath xpath = schemaConf.getXPath();
newSchema.loadFields(document, xpath);
if (null != uniqueKeyField) {
newSchema.requiredFields.add(uniqueKeyField);
}
//Run the callbacks on SchemaAware now that everything else is done
for (SchemaAware aware : newSchema.schemaAware) {
aware.inform(newSchema);
}
newSchema.refreshAnalyzers();
newSchema.schemaZkVersion = schemaZkVersion;
} catch (SolrException e) {
throw e;
} catch (Exception e) {
throw new SolrException(ErrorCode.SERVER_ERROR, "Schema Parsing Failed: " + e.getMessage(), e);
}
return newSchema;
}
private ManagedIndexSchema(final SolrConfig solrConfig, final SolrResourceLoader loader, boolean isMutable,
String managedSchemaResourceName, int schemaZkVersion, Object schemaUpdateLock)
throws KeeperException, InterruptedException {
super(solrConfig, loader);
this.isMutable = isMutable;
this.managedSchemaResourceName = managedSchemaResourceName;
this.schemaZkVersion = schemaZkVersion;
this.schemaUpdateLock = schemaUpdateLock;
}
/**
* Makes a shallow copy of this schema.
*
* Not copied: analyzers
*
* @param includeFieldDataStructures if true, fields, fieldsWithDefaultValue, and requiredFields
* are copied; otherwise, they are not.
* @return A shallow copy of this schema
*/
private ManagedIndexSchema shallowCopy(boolean includeFieldDataStructures) {
ManagedIndexSchema newSchema = null;
try {
newSchema = new ManagedIndexSchema
(solrConfig, loader, isMutable, managedSchemaResourceName, schemaZkVersion, getSchemaUpdateLock());
} catch (KeeperException e) {
final String msg = "Error instantiating ManagedIndexSchema";
log.error(msg, e);
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, msg, e);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
log.warn("", e);
}
assert newSchema != null;
newSchema.name = name;
newSchema.version = version;
newSchema.defaultSearchFieldName = defaultSearchFieldName;
newSchema.queryParserDefaultOperator = queryParserDefaultOperator;
newSchema.isExplicitQueryParserDefaultOperator = isExplicitQueryParserDefaultOperator;
newSchema.similarity = similarity;
newSchema.similarityFactory = similarityFactory;
newSchema.isExplicitSimilarity = isExplicitSimilarity;
newSchema.uniqueKeyField = uniqueKeyField;
newSchema.uniqueKeyFieldName = uniqueKeyFieldName;
newSchema.uniqueKeyFieldType = uniqueKeyFieldType;
if (includeFieldDataStructures) {
// These need new collections, since addFields() can add members to them
newSchema.fields.putAll(fields);
newSchema.fieldsWithDefaultValue.addAll(fieldsWithDefaultValue);
newSchema.requiredFields.addAll(requiredFields);
}
// These don't need new collections - addFields() won't add members to them
newSchema.fieldTypes = fieldTypes;
newSchema.dynamicFields = dynamicFields;
newSchema.dynamicCopyFields = dynamicCopyFields;
newSchema.copyFieldsMap = copyFieldsMap;
newSchema.copyFieldTargetCounts = copyFieldTargetCounts;
newSchema.schemaAware = schemaAware;
return newSchema;
}
public Object getSchemaUpdateLock() {
return schemaUpdateLock;
}
}