/* * Copyright 2011 Sonian Inc. * * 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 com.sonian.elasticsearch.http.jetty; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.xml.XmlConfiguration; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.PortsRange; import org.elasticsearch.env.Environment; import org.elasticsearch.http.*; import org.elasticsearch.transport.BindTransportException; import java.io.File; import java.net.*; import java.nio.channels.ServerSocketChannel; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; /** * @author imotov */ public class JettyHttpServerTransport extends AbstractLifecycleComponent<HttpServerTransport> implements HttpServerTransport { public static final String TRANSPORT_ATTRIBUTE = "com.sonian.elasticsearch.http.jetty.transport"; private final NetworkService networkService; private final String port; private final String bindHost; private final String publishHost; private final String[] jettyConfig; private final String jettyConfigServerId; private final Environment environment; private final ESLoggerWrapper loggerWrapper; private final ClusterName clusterName; private final Client client; private volatile BoundTransportAddress boundAddress; private volatile Server jettyServer; private volatile HttpServerAdapter httpServerAdapter; @Inject public JettyHttpServerTransport(Settings settings, Environment environment, NetworkService networkService, ESLoggerWrapper loggerWrapper, ClusterName clusterName, Client client) { super(settings); this.environment = environment; this.networkService = networkService; this.port = componentSettings.get("port", settings.get("http.port", "9200-9300")); this.bindHost = componentSettings.get("bind_host", settings.get("http.bind_host", settings.get("http.host"))); this.publishHost = componentSettings.get("publish_host", settings.get("http.publish_host", settings.get("http.host"))); this.jettyConfig = componentSettings.getAsArray("config", new String[]{"jetty.xml"}); this.jettyConfigServerId = componentSettings.get("server_id", "ESServer"); this.loggerWrapper = loggerWrapper; this.clusterName = clusterName; this.client = client; } @Override protected void doStart() throws ElasticsearchException { PortsRange portsRange = new PortsRange(port); final AtomicReference<Exception> lastException = new AtomicReference<Exception>(); Log.setLog(loggerWrapper); portsRange.iterate(new PortsRange.PortCallback() { @Override public boolean onPortNumber(int portNumber) { try { Server server = null; XmlConfiguration lastXmlConfiguration = null; Object[] objs = new Object[jettyConfig.length]; Map<String, String> esProperties = jettySettings(bindHost, portNumber); for (int i = 0; i < jettyConfig.length; i++) { String configFile = jettyConfig[i]; URL config = environment.resolveConfig(configFile); XmlConfiguration xmlConfiguration = new XmlConfiguration(config); // Make ids of objects created in early configurations available // in the later configurations if (lastXmlConfiguration != null) { xmlConfiguration.getIdMap().putAll(lastXmlConfiguration.getIdMap()); } else { xmlConfiguration.getIdMap().put("ESServerTransport", JettyHttpServerTransport.this); xmlConfiguration.getIdMap().put("ESClient", client); } // Inject elasticsearch properties xmlConfiguration.getProperties().putAll(esProperties); objs[i] = xmlConfiguration.configure(); lastXmlConfiguration = xmlConfiguration; } // Find jetty Server with id jettyConfigServerId Object serverObject = lastXmlConfiguration.getIdMap().get(jettyConfigServerId); if (serverObject != null) { if (serverObject instanceof Server) { server = (Server) serverObject; } } else { // For compatibility - if it's not available, find first available jetty Server for (Object obj : objs) { if (obj instanceof Server) { server = (Server) obj; break; } } } if (server == null) { logger.error("Cannot find server with id [{}] in configuration files [{}]", jettyConfigServerId, jettyConfig); lastException.set(new ElasticsearchException("Cannot find server with id " + jettyConfigServerId)); return true; } // Keep it for now for backward compatibility with previous versions of jetty.xml server.setAttribute(TRANSPORT_ATTRIBUTE, JettyHttpServerTransport.this); // Start all lifecycle objects configured by xml configurations for (Object obj : objs) { if (obj instanceof LifeCycle) { LifeCycle lifeCycle = (LifeCycle) obj; if (!lifeCycle.isRunning()) { lifeCycle.start(); } } } jettyServer = server; lastException.set(null); } catch (BindException e) { lastException.set(e); return false; } catch (Exception e) { logger.error("Jetty Startup Failed ", e); lastException.set(e); return true; } return true; } }); if (lastException.get() != null) { throw new BindHttpException("Failed to bind to [" + port + "]", lastException.get()); } InetSocketAddress jettyBoundAddress = findFirstInetConnector(jettyServer); if (jettyBoundAddress != null) { InetSocketAddress publishAddress; try { publishAddress = new InetSocketAddress(networkService.resolvePublishHostAddress(publishHost), jettyBoundAddress.getPort()); } catch (Exception e) { throw new BindTransportException("Failed to resolve publish address", e); } this.boundAddress = new BoundTransportAddress(new InetSocketTransportAddress(jettyBoundAddress), new InetSocketTransportAddress(publishAddress)); } else { throw new BindHttpException("Failed to find a jetty connector with Inet transport"); } } private InetSocketAddress findFirstInetConnector(Server server) { Connector[] connectors = server.getConnectors(); if (connectors != null) { for (Connector connector : connectors) { Object connection = connector.getConnection(); if (connection instanceof ServerSocketChannel) { SocketAddress address = ((ServerSocketChannel) connector.getConnection()).socket().getLocalSocketAddress(); if (address instanceof InetSocketAddress) { return (InetSocketAddress) address; } } else if (connection instanceof ServerSocket) { SocketAddress address = ((ServerSocket) connector.getConnection()).getLocalSocketAddress(); if (address instanceof InetSocketAddress) { return (InetSocketAddress) address; } } } } return null; } @Override protected void doStop() throws ElasticsearchException { if (jettyServer != null) { try { jettyServer.stop(); } catch (Exception ex) { throw new ElasticsearchException("Cannot stop jetty server", ex); } jettyServer = null; } } @Override protected void doClose() throws ElasticsearchException { } @Override public BoundTransportAddress boundAddress() { return this.boundAddress; } @Override public HttpInfo info() { return new HttpInfo(boundAddress(), 0); } @Override public HttpStats stats() { return new HttpStats(0, 0); } @Override public void httpServerAdapter(HttpServerAdapter httpServerAdapter) { this.httpServerAdapter = httpServerAdapter; } public HttpServerAdapter httpServerAdapter() { return httpServerAdapter; } public Settings settings() { return settings; } public Settings componentSettings() { return componentSettings; } private Map<String, String> jettySettings(String hostAddress, int port) { MapBuilder<String, String> jettySettings = MapBuilder.newMapBuilder(); jettySettings.put("es.home", environment.homeFile().getAbsolutePath()); jettySettings.put("es.config", environment.configFile().getAbsolutePath()); jettySettings.put("es.data", getAbsolutePaths(environment.dataFiles())); jettySettings.put("es.cluster.data", getAbsolutePaths(environment.dataWithClusterFiles())); jettySettings.put("es.cluster", clusterName.value()); if (hostAddress != null) { jettySettings.put("jetty.bind_host", hostAddress); } for (Map.Entry<String, String> entry : componentSettings.getAsMap().entrySet()) { jettySettings.put("jetty." + entry.getKey(), entry.getValue()); } // Override jetty port in case we have a port-range jettySettings.put("jetty.port", String.valueOf(port)); return jettySettings.immutableMap(); } private String getAbsolutePaths(File[] files) { StringBuilder buf = new StringBuilder(); for (File file : files) { if (buf.length() > 0) { buf.append(','); } buf.append(file.getAbsolutePath()); } return buf.toString(); } }