/**
* 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.cxf.transport.http_undertow;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.management.MBeanServer;
import org.apache.cxf.Bus;
import org.apache.cxf.buslifecycle.BusLifeCycleListener;
import org.apache.cxf.buslifecycle.BusLifeCycleManager;
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.configuration.jsse.TLSServerParameters;
import org.apache.cxf.management.InstrumentationManager;
/**
* This Bus Extension handles the configuration of network port
* numbers for use with "http" or "https". This factory
* caches the UndertowHTTPServerEngines so that they may be
* retrieved if already previously configured.
*/
@NoJSR250Annotations(unlessNull = "bus")
public class UndertowHTTPServerEngineFactory {
private static final Logger LOG =
LogUtils.getL7dLogger(UndertowHTTPServerEngineFactory.class);
private static final int FALLBACK_THREADING_PARAMS_KEY = 0;
/**
* This map holds references for allocated ports.
*/
// Still use the static map to hold the port information
// in the same JVM
private static ConcurrentHashMap<Integer, UndertowHTTPServerEngine> portMap =
new ConcurrentHashMap<Integer, UndertowHTTPServerEngine>();
private BusLifeCycleManager lifeCycleManager;
/**
* This map holds the threading parameters that are to be applied
* to new Engines when bound to the reference id.
*/
private Map<String, ThreadingParameters> threadingParametersMap =
new TreeMap<String, ThreadingParameters>();
private ThreadingParameters fallbackThreadingParameters;
/**
* This map holds TLS Server Parameters that are to be used to
* configure a subsequently created UndertowHTTPServerEngine.
*/
private Map<String, TLSServerParameters> tlsParametersMap =
new TreeMap<String, TLSServerParameters>();
/**
* The bus.
*/
private Bus bus;
public UndertowHTTPServerEngineFactory() {
// Empty
}
public UndertowHTTPServerEngineFactory(Bus b) {
setBus(b);
}
public UndertowHTTPServerEngineFactory(Bus b,
Map<String, TLSServerParameters> tls,
Map<String, ThreadingParameters> threading) {
tlsParametersMap.putAll(tls);
threadingParametersMap.putAll(threading);
setBus(b);
}
private static UndertowHTTPServerEngine getOrCreate(UndertowHTTPServerEngineFactory factory,
String host,
int port,
TLSServerParameters tlsParams) throws IOException, GeneralSecurityException {
UndertowHTTPServerEngine ref = portMap.get(port);
if (ref == null) {
ref = new UndertowHTTPServerEngine(host, port);
if (tlsParams != null) {
ref.setTlsServerParameters(tlsParams);
}
UndertowHTTPServerEngine tmpRef = portMap.putIfAbsent(port, ref);
ref.finalizeConfig();
if (tmpRef != null) {
ref = tmpRef;
}
}
return ref;
}
/**
* This call is used to set the bus. It should only be called once.
* @param bus
*/
@Resource(name = "cxf")
public final void setBus(Bus bus) {
this.bus = bus;
if (bus != null) {
bus.setExtension(this, UndertowHTTPServerEngineFactory.class);
lifeCycleManager = bus.getExtension(BusLifeCycleManager.class);
if (null != lifeCycleManager) {
lifeCycleManager.registerLifeCycleListener(new UndertowBusLifeCycleListener());
}
}
}
private class UndertowBusLifeCycleListener implements BusLifeCycleListener {
public void initComplete() {
UndertowHTTPServerEngineFactory.this.initComplete();
}
public void preShutdown() {
UndertowHTTPServerEngineFactory.this.preShutdown();
}
public void postShutdown() {
UndertowHTTPServerEngineFactory.this.postShutdown();
}
}
public Bus getBus() {
return bus;
}
/**
* This call sets TLSParametersMap for a UndertowHTTPServerEngine
*
*/
public void setTlsServerParametersMap(
Map<String, TLSServerParameters> tlsParamsMap) {
tlsParametersMap = tlsParamsMap;
}
public Map<String, TLSServerParameters> getTlsServerParametersMap() {
return tlsParametersMap;
}
public void setEnginesList(List<UndertowHTTPServerEngine> enginesList) {
for (UndertowHTTPServerEngine engine : enginesList) {
if (engine.getPort() == FALLBACK_THREADING_PARAMS_KEY) {
fallbackThreadingParameters = engine.getThreadingParameters();
}
portMap.putIfAbsent(engine.getPort(), engine);
}
}
/**
* This call sets the ThreadingParameters for a UndertowHTTPServerEngine
*
*/
public void setThreadingParametersMap(
Map<String, ThreadingParameters> threadingParamsMap) {
threadingParametersMap = threadingParamsMap;
}
public Map<String, ThreadingParameters> getThreadingParametersMap() {
return threadingParametersMap;
}
/**
* This call sets TLSServerParameters for a UndertowHTTPServerEngine
* that will be subsequently created. It will not alter an engine
* that has already been created for that network port.
* @param host if not null, server will listen on this address/host,
* otherwise, server will listen on all local addresses.
* @param port The network port number to bind to the engine.
* @param tlsParams The tls server parameters. Cannot be null.
* @throws IOException
* @throws GeneralSecurityException
*/
public void setTLSServerParametersForPort(
String host,
int port,
TLSServerParameters tlsParams) throws GeneralSecurityException, IOException {
if (tlsParams == null) {
throw new IllegalArgumentException("tlsParams cannot be null");
}
UndertowHTTPServerEngine ref = retrieveUndertowHTTPServerEngine(port);
if (null == ref) {
getOrCreate(this, host, port, tlsParams);
} else {
ref.setTlsServerParameters(tlsParams);
}
}
/**
* calls thru to {{@link #createUndertowHTTPServerEngine(String, int, String)} with 'null' for host value
*/
public void setTLSServerParametersForPort(
int port,
TLSServerParameters tlsParams) throws GeneralSecurityException, IOException {
setTLSServerParametersForPort(null, port, tlsParams);
}
/**
* This call retrieves a previously configured UndertowHTTPServerEngine for the
* given port. If none exists, this call returns null.
*/
public synchronized UndertowHTTPServerEngine retrieveUndertowHTTPServerEngine(int port) {
return portMap.get(port);
}
/**
* This call creates a new UndertowHTTPServerEngine initialized for "http"
* or "https" on the given port. The determination of "http" or "https"
* will depend on configuration of the engine's bean name.
*
* If an UndertowHTTPEngine already exists, or the port
* is already in use, a BindIOException will be thrown. If the
* engine is being Spring configured for TLS a GeneralSecurityException
* may be thrown.
*
* @param host if not null, server will listen on this host/address, otherwise
* server will listen on all local addresses.
* @param port listen port for server
* @param protocol "http" or "https"
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public synchronized UndertowHTTPServerEngine createUndertowHTTPServerEngine(String host, int port,
String protocol) throws GeneralSecurityException, IOException {
LOG.fine("Creating Undertow HTTP Server Engine for port " + port + ".");
UndertowHTTPServerEngine ref = getOrCreate(this, host, port, null);
// checking the protocol
if (!protocol.equals(ref.getProtocol())) {
throw new IOException("Protocol mismatch for port " + port + ": "
+ "engine's protocol is " + ref.getProtocol()
+ ", the url protocol is " + protocol);
}
if (!(ref.isSetThreadingParameters()
|| null == fallbackThreadingParameters)) {
if (LOG.isLoggable(Level.INFO)) {
final int min = fallbackThreadingParameters.getMinThreads();
final int max = fallbackThreadingParameters.getMaxThreads();
LOG.log(Level.INFO,
"FALLBACK_THREADING_PARAMETERS_MSG",
new Object[] {port, min, max, ""});
}
ref.setThreadingParameters(fallbackThreadingParameters);
}
return ref;
}
/**
* Calls thru to {{@link #createUndertowHTTPServerEngine(String, int, String)} with a 'null' host value
*/
public synchronized UndertowHTTPServerEngine createUndertowHTTPServerEngine(int port,
String protocol) throws GeneralSecurityException, IOException {
return createUndertowHTTPServerEngine(null, port, protocol);
}
/**
* This method removes the Server Engine from the port map and stops it.
*/
public static synchronized void destroyForPort(int port) {
UndertowHTTPServerEngine ref = portMap.remove(port);
if (ref != null) {
LOG.fine("Stopping Undertow HTTP Server Engine on port " + port + ".");
try {
ref.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public MBeanServer getMBeanServer() {
if (bus != null && bus.getExtension(InstrumentationManager.class) != null) {
return bus.getExtension(InstrumentationManager.class).getMBeanServer();
}
return null;
}
public void initComplete() {
// do nothing here
}
public void postShutdown() {
// shut down the Undertow server in the portMap
// To avoid the CurrentModificationException,
// do not use portMap.values directly
UndertowHTTPServerEngine[] engines =
portMap.values().toArray(new UndertowHTTPServerEngine[portMap.values().size()]);
for (UndertowHTTPServerEngine engine : engines) {
engine.shutdown();
}
// clean up the collections
threadingParametersMap.clear();
tlsParametersMap.clear();
}
public void preShutdown() {
// do nothing here
// just let server registry to call the server stop first
}
}