/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.search.elasticsearch.internal.connection;
import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.security.SecureRandomUtil;
import com.liferay.portal.kernel.util.PortalRunMode;
import com.liferay.portal.kernel.util.Props;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.SystemProperties;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration;
import com.liferay.portal.search.elasticsearch.connection.ElasticsearchConnection;
import com.liferay.portal.search.elasticsearch.connection.OperationMode;
import com.liferay.portal.search.elasticsearch.index.IndexFactory;
import com.liferay.portal.search.elasticsearch.internal.cluster.ClusterSettingsContext;
import com.liferay.portal.search.elasticsearch.settings.SettingsContributor;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Map;
import org.apache.commons.lang.time.StopWatch;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.discovery.DiscoveryService;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.action.SearchServiceTransportAction;
import org.elasticsearch.search.fetch.QueryFetchSearchResult;
import org.elasticsearch.search.internal.ShardSearchTransportRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.jboss.netty.util.internal.ByteBufferUtil;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
/**
* @author Michael C. Han
*/
@Component(
configurationPid = "com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration",
immediate = true, property = {"operation.mode=EMBEDDED"},
service = ElasticsearchConnection.class
)
public class EmbeddedElasticsearchConnection
extends BaseElasticsearchConnection {
@Override
public void close() {
super.close();
if (_node == null) {
return;
}
try {
Class.forName(ByteBufferUtil.class.getName());
}
catch (ClassNotFoundException cnfe) {
if (_log.isWarnEnabled()) {
_log.warn(
"Unable to preload " + ByteBufferUtil.class +
" to prevent Netty shutdown concurrent class loading " +
"interruption issue",
cnfe);
}
}
_node.close();
_node = null;
}
public Node getNode() {
return _node;
}
@Override
public OperationMode getOperationMode() {
return OperationMode.EMBEDDED;
}
@Override
@Reference(unbind = "-")
public void setIndexFactory(IndexFactory indexFactory) {
super.setIndexFactory(indexFactory);
}
@Activate
@Modified
protected void activate(Map<String, Object> properties) {
elasticsearchConfiguration = ConfigurableUtil.createConfigurable(
ElasticsearchConfiguration.class, properties);
}
@Override
@Reference(
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY,
target = "(operation.mode=EMBEDDED)"
)
protected void addSettingsContributor(
SettingsContributor settingsContributor) {
super.addSettingsContributor(settingsContributor);
}
protected void configureClustering() {
settingsBuilder.put(
"cluster.name", elasticsearchConfiguration.clusterName());
settingsBuilder.put(
"cluster.routing.allocation.disk.threshold_enabled", false);
settingsBuilder.put("discovery.zen.ping.multicast.enabled", false);
}
protected void configureHttp() {
settingsBuilder.put(
"http.enabled", elasticsearchConfiguration.httpEnabled());
if (!elasticsearchConfiguration.httpEnabled()) {
return;
}
settingsBuilder.put(
"http.cors.enabled", elasticsearchConfiguration.httpCORSEnabled());
if (!elasticsearchConfiguration.httpCORSEnabled()) {
return;
}
settingsBuilder.put(
"http.cors.allow-origin",
elasticsearchConfiguration.httpCORSAllowOrigin());
String httpCORSConfigurations =
elasticsearchConfiguration.httpCORSConfigurations();
if (Validator.isNotNull(httpCORSConfigurations)) {
settingsBuilder.loadFromSource(httpCORSConfigurations);
}
}
protected void configureNetworking() {
String networkBindHost = elasticsearchConfiguration.networkBindHost();
if (Validator.isNotNull(networkBindHost)) {
settingsBuilder.put("network.bind.host", networkBindHost);
}
String networkHost = elasticsearchConfiguration.networkHost();
if (Validator.isNull(networkBindHost) &&
Validator.isNull(networkHost) &&
Validator.isNull(elasticsearchConfiguration.networkPublishHost())) {
InetAddress localBindInetAddress =
clusterSettingsContext.getLocalBindInetAddress();
if (localBindInetAddress != null) {
networkHost = localBindInetAddress.getHostAddress();
}
}
if (Validator.isNotNull(networkHost)) {
settingsBuilder.put("network.host", networkHost);
}
String networkPublishHost =
elasticsearchConfiguration.networkPublishHost();
if (Validator.isNotNull(networkPublishHost)) {
settingsBuilder.put("network.publish.host", networkPublishHost);
}
String transportTcpPort = elasticsearchConfiguration.transportTcpPort();
if (Validator.isNotNull(transportTcpPort)) {
settingsBuilder.put("transport.tcp.port", transportTcpPort);
}
}
protected void configurePaths() {
settingsBuilder.put(
"path.data",
props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch/indices");
settingsBuilder.put(
"path.home",
props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch");
settingsBuilder.put(
"path.logs", props.get(PropsKeys.LIFERAY_HOME) + "/logs");
settingsBuilder.put(
"path.plugins",
props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch/plugins");
settingsBuilder.put(
"path.repo",
props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch/repo");
settingsBuilder.put(
"path.work", SystemProperties.get(SystemProperties.TMP_DIR));
}
protected void configurePlugin(String name, Settings settings) {
EmbeddedElasticsearchPluginManager embeddedElasticsearchPluginManager =
new EmbeddedElasticsearchPluginManager(
name, settings.get("path.plugins"),
new PluginManagerFactoryImpl(settings),
new PluginZipFactoryImpl());
try {
embeddedElasticsearchPluginManager.install();
}
catch (IOException ioe) {
throw new RuntimeException(
"Unable to install " + name + " plugin", ioe);
}
}
protected void configurePlugins() {
Settings settings = settingsBuilder.build();
String[] plugins = {
"analysis-icu", "analysis-kuromoji", "analysis-smartcn",
"analysis-stempel"
};
for (String plugin : plugins) {
configurePlugin(plugin, settings);
}
}
@Override
protected Client createClient() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
if (_log.isWarnEnabled()) {
StringBundler sb = new StringBundler(6);
sb.append("Liferay is configured to use embedded Elasticsearch ");
sb.append("as its search engine. Do NOT use embedded ");
sb.append("Elasticsearch in production. Embedded Elasticsearch ");
sb.append("is useful for development and demonstration purposes. ");
sb.append("Remote Elasticsearch connections can be configured in ");
sb.append("the Control Panel.");
_log.warn(sb);
}
if (_log.isDebugEnabled()) {
_log.debug(
"Starting embedded Elasticsearch cluster " +
elasticsearchConfiguration.clusterName());
}
_node = createNode(settingsBuilder.build());
_node.start();
Client client = _node.client();
if (_log.isDebugEnabled()) {
stopWatch.stop();
_log.debug(
"Finished starting " +
elasticsearchConfiguration.clusterName() + " in " +
stopWatch.getTime() + " ms");
}
return client;
}
protected Node createNode(Settings settings) {
Thread thread = Thread.currentThread();
ClassLoader contextClassLoader = thread.getContextClassLoader();
Class<?> clazz = getClass();
thread.setContextClassLoader(clazz.getClassLoader());
try {
NodeBuilder nodeBuilder = new NodeBuilder();
nodeBuilder.settings(settings);
nodeBuilder.local(true);
Node node = nodeBuilder.build();
if (elasticsearchConfiguration.syncSearch()) {
Injector injector = node.injector();
TransportService transportService = injector.getInstance(
TransportService.class);
transportService.removeHandler(
SearchServiceTransportAction.QUERY_FETCH_ACTION_NAME);
SearchService searchService = injector.getInstance(
SearchService.class);
transportService.registerRequestHandler(
SearchServiceTransportAction.QUERY_FETCH_ACTION_NAME,
ShardSearchTransportRequest.class, ThreadPool.Names.SAME,
(request, channel) -> {
QueryFetchSearchResult queryFetchSearchResult =
searchService.executeFetchPhase(request);
channel.sendResponse(queryFetchSearchResult);
});
}
return node;
}
finally {
thread.setContextClassLoader(contextClassLoader);
}
}
@Deactivate
protected void deactivate(Map<String, Object> properties) {
close();
}
@Override
protected void loadRequiredDefaultConfigurations() {
settingsBuilder.put("action.auto_create_index", false);
settingsBuilder.put(
"bootstrap.mlockall",
elasticsearchConfiguration.bootstrapMlockAll());
configureClustering();
configureHttp();
settingsBuilder.put("index.number_of_replicas", 0);
settingsBuilder.put("index.number_of_shards", 1);
configureNetworking();
settingsBuilder.put("node.client", false);
settingsBuilder.put("node.data", true);
settingsBuilder.put(
DiscoveryService.SETTING_DISCOVERY_SEED,
SecureRandomUtil.nextLong());
configurePaths();
configurePlugins();
if (PortalRunMode.isTestMode()) {
settingsBuilder.put("index.refresh_interval", "1ms");
settingsBuilder.put("index.translog.flush_threshold_ops", "1");
settingsBuilder.put("index.translog.interval", "1ms");
}
}
@Override
protected void removeSettingsContributor(
SettingsContributor settingsContributor) {
super.removeSettingsContributor(settingsContributor);
}
@Reference
protected ClusterSettingsContext clusterSettingsContext;
@Reference
protected Props props;
private static final Log _log = LogFactoryUtil.getLog(
EmbeddedElasticsearchConnection.class);
private Node _node;
}