/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed 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.springframework.data.solr.test.util;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.FileUtils;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryRequestBase;
import org.apache.solr.servlet.SolrRequestParsers;
import org.apache.solr.util.RTimerTree;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.core.io.Resource;
import org.springframework.data.solr.server.SolrClientFactory;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link ExternalResource} wrapping a {@link CoreContainer} allowing easy access to {@link SolrClient} for cores
* configured via an {@link Resource}. Configuration options will be copied to a temp folder and removed afterwards.
*
* @author Christoph Strobl
*/
public class EmbeddedSolrServer extends ExternalResource implements SolrClientFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(EmbeddedSolrServer.class);
Resource configDir;
TemporaryFolder folder;
CoreContainer coreContainer;
private ClientCache clientCache = ClientCache.DISABLED;
ConcurrentHashMap<String, SolrClient> cachedClients = new ConcurrentHashMap<>();
private EmbeddedSolrServer() {}
/**
* Get a configured {@link EmbeddedSolrServer} using the configDir as configuration source.
*
* @param configDir must not be {@literal null}.
* @return
*/
public static EmbeddedSolrServer configure(Resource configDir) {
return configure(configDir, ClientCache.DISABLED);
}
/**
* Get a configured {@link EmbeddedSolrServer} using the configDir as configuration source.
*
* @param configDir must not be {@literal null}.
* @param clientCache
* @return
*/
public static EmbeddedSolrServer configure(Resource configDir, ClientCache clientCache) {
EmbeddedSolrServer essr = new EmbeddedSolrServer();
essr.configDir = configDir;
essr.clientCache = clientCache;
return essr;
}
/*
* (non-Javadoc)
* @see org.springframework.data.solr.server.SolrClientFactory#getSolrClient()
*/
@Override
public SolrClient getSolrClient() {
return getSolrClient("collection1");
}
/*
* (non-Javadoc)
* @see org.springframework.data.solr.server.SolrClientFactory#getSolrClient(java.lang.String)
*/
@SuppressWarnings("serial")
public SolrClient getSolrClient(String collectionName) {
if (ClientCache.ENABLED.equals(clientCache) && cachedClients.containsKey(collectionName)) {
return cachedClients.get(collectionName);
}
org.apache.solr.client.solrj.embedded.EmbeddedSolrServer solrServer = new org.apache.solr.client.solrj.embedded.EmbeddedSolrServer(
coreContainer, collectionName) {
public void shutdown() {
// ignore close at this point. CoreContainer will be shut down on its own.
}
@Override
public void close() {
shutdown();
}
};
final DirectFieldAccessor dfa = new DirectFieldAccessor(solrServer);
dfa.setPropertyValue("_parser", new HttpMethodGuessingSolrRequestParsers());
if (ClientCache.ENABLED.equals(clientCache)) {
cachedClients.put(collectionName, solrServer);
}
return solrServer;
}
/*
* (non-Javadoc)
* @see org.springframework.data.solr.server.SolrClientFactory#getCores()
*/
public List<String> getCores() {
return new ArrayList<>(coreContainer.getCoreNames());
}
/*
* (non-Javadoc)
* @see org.junit.rules.ExternalResource#before()
*/
@Override
protected void before() throws Throwable {
folder = new TemporaryFolder();
folder.create();
LOGGER.debug(String.format("Created temp folder %s", folder.getRoot().getPath()));
if (configDir != null && configDir.exists() && configDir.getFile().isDirectory()) {
FileUtils.copyDirectory(configDir.getFile(), folder.getRoot());
LOGGER.debug(String.format("Copied %s files from %s to temp folder.", configDir.getFile().list().length,
configDir.getFile().getPath()));
}
init(folder.getRoot().getPath());
}
/*
* (non-Javadoc)
* @see org.junit.rules.ExternalResource#after()
*/
@Override
protected void after() {
LOGGER.debug("Shutting down CoreContainer");
try {
coreContainer.shutdown();
coreContainer = null;
cachedClients.clear();
} catch (Exception e) {
LOGGER.error("Error shutting down CoreContainer", e);
}
LOGGER.debug(String.format("Removing temp folder %s", folder.getRoot().getPath()));
folder.delete();
}
public void init(String solrHome) throws SolrServerException, IOException, InterruptedException {
Method createAndLoadMethod = ClassUtils.getStaticMethod(CoreContainer.class, "createAndLoad", String.class,
File.class);
LOGGER.debug("Starting CoreContainer %s and loading cores.");
if (createAndLoadMethod != null) {
coreContainer = (CoreContainer) ReflectionUtils.invokeMethod(createAndLoadMethod, null, solrHome,
new File(solrHome + "/solr.xml"));
} else {
createAndLoadMethod = ClassUtils.getStaticMethod(CoreContainer.class, "createAndLoad", Path.class, Path.class);
coreContainer = (CoreContainer) ReflectionUtils.invokeMethod(createAndLoadMethod, null,
FileSystems.getDefault().getPath(solrHome),
FileSystems.getDefault().getPath(new File(solrHome + "/solr.xml").getPath()));
}
Thread.sleep(15); // just a little time to make sure the core container is initialized fully
LOGGER.debug("CoreContainer up and running - Happy searching :)");
}
/**
* Workaround for {@link SolrRequestParsers} which does not read POST requests correctly and treats them as get ones.
* This happens as the context is not set up correctly since the used http request gets {@code nulled} out :( <br />
* So we check if there's a {@link ContentStream} available and treat all those requests as POST ones.
*
* @author Christoph Strobl
*/
static class HttpMethodGuessingSolrRequestParsers extends SolrRequestParsers {
HttpMethodGuessingSolrRequestParsers() {
this(null);
}
HttpMethodGuessingSolrRequestParsers(SolrConfig globalConfig) {
super(globalConfig);
}
/*
* (non-Javadoc)
* @see org.apache.solr.servlet.SolrRequestParsers#buildRequestFrom(org.apache.solr.core.SolrCore, org.apache.solr.common.params.SolrParams, java.util.Collection)
*/
@Override
public SolrQueryRequest buildRequestFrom(SolrCore core, SolrParams params, Collection<ContentStream> streams)
throws Exception {
if (CollectionUtils.isEmpty(streams)) {
return super.buildRequestFrom(core, params, streams);
}
MockHttpServletRequest mock = new MockHttpServletRequest();
mock.setMethod("POST");
Method buildRequestFromMethod = org.springframework.util.ReflectionUtils.findMethod(this.getClass(),
"buildRequestFrom", SolrCore.class, SolrParams.class, Collection.class, RTimerTree.class,
HttpServletRequest.class);
buildRequestFromMethod.setAccessible(true);
SolrQueryRequestBase sqr = (SolrQueryRequestBase) buildRequestFromMethod.invoke(this, core, params, streams,
new RTimerTree(), mock);
if (sqr.getContext() == null) {
new DirectFieldAccessor(sqr).setPropertyValue("context", Collections.singletonMap("httpMethod", "POST"));
} else {
sqr.getContext().put("httpMethod", "POST");
}
return sqr;
}
}
public enum ClientCache {
ENABLED, DISABLED
}
}