blob: 6d65f86705f45819aee7132501f71c836df0844e [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.core;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.solr.cloud.CloudConfigSetService;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.ConfigNode;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.IndexSchemaFactory;
import org.apache.solr.util.DOMConfigNode;
import org.apache.solr.util.DataConfigNode;
import org.apache.solr.util.SystemIdResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import static org.apache.solr.schema.IndexSchema.SCHEMA;
import static org.apache.solr.schema.IndexSchema.SLASH;
/**
* Service class used by the CoreContainer to load ConfigSets for use in SolrCore
* creation.
*/
public abstract class ConfigSetService {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public static ConfigSetService createConfigSetService(NodeConfig nodeConfig, SolrResourceLoader loader, ZkController zkController) {
if (zkController == null) {
return new Standalone(loader, nodeConfig.hasSchemaCache(), nodeConfig.getConfigSetBaseDirectory());
} else {
return new CloudConfigSetService(loader, nodeConfig.hasSchemaCache(), zkController);
}
}
protected final SolrResourceLoader parentLoader;
/** Optional cache of schemas, key'ed by a bunch of concatenated things */
private final Cache<String, IndexSchema> schemaCache;
/**
* Load the ConfigSet for a core
* @param dcore the core's CoreDescriptor
* @return a ConfigSet
*/
@SuppressWarnings({"rawtypes"})
public final ConfigSet loadConfigSet(CoreDescriptor dcore) {
SolrResourceLoader coreLoader = createCoreResourceLoader(dcore);
try {
// ConfigSet properties are loaded from ConfigSetProperties.DEFAULT_FILENAME file.
NamedList properties = loadConfigSetProperties(dcore, coreLoader);
// ConfigSet flags are loaded from the metadata of the ZK node of the configset.
NamedList flags = loadConfigSetFlags(dcore, coreLoader);
boolean trusted =
(coreLoader instanceof ZkSolrResourceLoader
&& flags != null
&& flags.get("trusted") != null
&& !flags.getBooleanArg("trusted")
) ? false: true;
SolrConfig solrConfig = createSolrConfig(dcore, coreLoader, trusted);
return new ConfigSet(configSetName(dcore), solrConfig, force -> createIndexSchema(dcore, solrConfig, force), properties, trusted);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Could not load conf for core " + dcore.getName() +
": " + e.getMessage(), e);
}
}
/**
* Create a new ConfigSetService
* @param loader the CoreContainer's resource loader
* @param shareSchema should we share the IndexSchema among cores of same config?
*/
public ConfigSetService(SolrResourceLoader loader, boolean shareSchema) {
this.parentLoader = loader;
this.schemaCache = shareSchema ? Caffeine.newBuilder().weakValues().build() : null;
}
/**
* Create a SolrConfig object for a core
* @param cd the core's CoreDescriptor
* @param loader the core's resource loader
* @param isTrusted is the configset trusted?
* @return a SolrConfig object
*/
protected SolrConfig createSolrConfig(CoreDescriptor cd, SolrResourceLoader loader, boolean isTrusted) {
return SolrConfig.readFromResourceLoader(loader, cd.getConfigName(), isTrusted, cd.getSubstitutableProperties());
}
/**
* Create an IndexSchema object for a core. It might be a cached lookup.
* @param cd the core's CoreDescriptor
* @param solrConfig the core's SolrConfig
* @return an IndexSchema
*/
protected IndexSchema createIndexSchema(CoreDescriptor cd, SolrConfig solrConfig, boolean forceFetch) {
// This is the schema name from the core descriptor. Sometimes users specify a custom schema file.
// Important: indexSchemaFactory.create wants this!
String cdSchemaName = cd.getSchemaName();
// This is the schema name that we think will actually be used. In the case of a managed schema,
// we don't know for sure without examining what files exists in the configSet, and we don't
// want to pay the overhead of that at this juncture. If we guess wrong, no schema sharing.
// The fix is usually to name your schema managed-schema instead of schema.xml.
IndexSchemaFactory indexSchemaFactory = IndexSchemaFactory.newIndexSchemaFactory(solrConfig);
String configSet = cd.getConfigSet();
if (configSet != null && schemaCache != null) {
String guessSchemaName = indexSchemaFactory.getSchemaResourceName(cdSchemaName);
Long modVersion = getCurrentSchemaModificationVersion(configSet, solrConfig, guessSchemaName);
if (modVersion != null) {
// note: luceneMatchVersion influences the schema
String cacheKey = configSet + "/" + guessSchemaName + "/" + modVersion + "/" + solrConfig.luceneMatchVersion;
return schemaCache.get(cacheKey,
(key) -> indexSchemaFactory.create(cdSchemaName, solrConfig, ConfigSetService.this));
} else {
log.warn("Unable to get schema modification version, configSet={} schema={}", configSet, guessSchemaName);
// see explanation above; "guessSchema" is a guess
}
}
return indexSchemaFactory.create(cdSchemaName, solrConfig, this);
}
/**
* Returns a modification version for the schema file.
* Null may be returned if not known, and if so it defeats schema caching.
*/
protected abstract Long getCurrentSchemaModificationVersion(String configSet, SolrConfig solrConfig, String schemaFile);
/**
* Return the ConfigSet properties or null if none.
* @see ConfigSetProperties
* @param cd the core's CoreDescriptor
* @param loader the core's resource loader
* @return the ConfigSet properties
*/
@SuppressWarnings({"rawtypes"})
protected NamedList loadConfigSetProperties(CoreDescriptor cd, SolrResourceLoader loader) {
return ConfigSetProperties.readFromResourceLoader(loader, cd.getConfigSetPropertiesName());
}
/**
* Return the ConfigSet flags or null if none.
*/
// TODO should fold into configSetProps -- SOLR-14059
@SuppressWarnings({"rawtypes"})
protected NamedList loadConfigSetFlags(CoreDescriptor cd, SolrResourceLoader loader) {
return null;
}
/**
* Create a SolrResourceLoader for a core
* @param cd the core's CoreDescriptor
* @return a SolrResourceLoader
*/
protected abstract SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd);
/**
* Return a name for the ConfigSet for a core to be used for printing/diagnostic purposes.
* @param cd the core's CoreDescriptor
* @return a name for the core's ConfigSet
*/
public abstract String configSetName(CoreDescriptor cd);
public interface ConfigResource {
ConfigNode get() throws Exception;
}
public static ConfigNode getParsedSchema(InputStream is, SolrResourceLoader loader, String name) throws IOException, SAXException, ParserConfigurationException {
XmlConfigFile schemaConf = null;
InputSource inputSource = new InputSource(is);
inputSource.setSystemId(SystemIdResolver.createSystemIdFromResourceName(name));
schemaConf = new XmlConfigFile(loader, SCHEMA, inputSource, SLASH + SCHEMA + SLASH, null);
return new DataConfigNode(new DOMConfigNode(schemaConf.getDocument().getDocumentElement()));
}
/**
* The Solr standalone version of ConfigSetService.
*
* Loads a ConfigSet defined by the core's configSet property,
* looking for a directory named for the configSet property value underneath
* a base directory. If no configSet property is set, loads the ConfigSet
* instead from the core's instance directory.
*/
public static class Standalone extends ConfigSetService {
private final Path configSetBase;
public Standalone(SolrResourceLoader loader, boolean shareSchema, Path configSetBase) {
super(loader, shareSchema);
this.configSetBase = configSetBase;
}
@Override
public SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd) {
Path instanceDir = locateInstanceDir(cd);
return new SolrResourceLoader(instanceDir, parentLoader.getClassLoader());
}
@Override
public String configSetName(CoreDescriptor cd) {
return (cd.getConfigSet() == null ? "instancedir " : "configset ") + locateInstanceDir(cd);
}
protected Path locateInstanceDir(CoreDescriptor cd) {
String configSet = cd.getConfigSet();
if (configSet == null)
return cd.getInstanceDir();
Path configSetDirectory = configSetBase.resolve(configSet);
if (!Files.isDirectory(configSetDirectory))
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Could not load configuration from directory " + configSetDirectory);
return configSetDirectory;
}
@Override
protected Long getCurrentSchemaModificationVersion(String configSet, SolrConfig solrConfig, String schemaFileName) {
Path schemaFile = Paths.get(solrConfig.getResourceLoader().getConfigDir()).resolve(schemaFileName);
try {
return Files.getLastModifiedTime(schemaFile).toMillis();
} catch (FileNotFoundException e) {
return null; // acceptable
} catch (IOException e) {
log.warn("Unexpected exception when getting modification time of {}", schemaFile, e);
return null; // debatable; we'll see an error soon if there's a real problem
}
}
}
}