blob: 58e516397403d5365130bb5f3ba76b6868e983bb [file] [log] [blame]
/*
* Copyright (c) 2007, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.enterprise.v3.services.impl;
import java.beans.PropertyChangeEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.config.dom.NetworkListener;
import org.glassfish.grizzly.config.dom.Http;
import org.glassfish.grizzly.config.dom.Protocol;
import org.glassfish.grizzly.config.dom.Ssl;
import org.glassfish.grizzly.config.dom.ThreadPool;
import org.glassfish.grizzly.config.dom.Transport;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.config.serverbeans.SystemProperty;
import org.jvnet.hk2.config.Changed;
import org.jvnet.hk2.config.ConfigBeanProxy;
import org.jvnet.hk2.config.ConfigListener;
import org.jvnet.hk2.config.ConfigSupport;
import org.jvnet.hk2.config.NotProcessed;
import org.jvnet.hk2.config.UnprocessedChangeEvents;
import com.sun.enterprise.config.serverbeans.VirtualServer;
import com.sun.enterprise.util.Result;
import org.glassfish.grizzly.config.dom.FileCache;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
import org.glassfish.grizzly.config.GrizzlyListener;
import org.glassfish.grizzly.config.dom.NetworkConfig;
import org.glassfish.grizzly.impl.FutureImpl;
import org.glassfish.grizzly.impl.SafeFutureImpl;
/**
* Grizzly dynamic configuration handler
*
* @author Alexey Stashok
*/
public class DynamicConfigListener implements ConfigListener {
private static final String ADMIN_LISTENER = "admin-listener";
private GrizzlyService grizzlyService;
private Config config;
private final Logger logger;
private static final int RECONFIG_LOCK_TIMEOUT_SEC = 30;
private static final ReentrantLock reconfigLock = new ReentrantLock();
private static final Map<Integer, FutureImpl> reconfigByPortLock =
new HashMap<Integer, FutureImpl>();
public DynamicConfigListener(final Config parent, final Logger logger) {
config = parent;
this.logger = logger;
}
@Override
public synchronized UnprocessedChangeEvents changed(final PropertyChangeEvent[] events) {
return ConfigSupport.sortAndDispatch(
events, new Changed() {
@Override
public <T extends ConfigBeanProxy> NotProcessed changed(TYPE type,
Class<T> tClass, T t) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "NetworkConfig changed {0} {1} {2}",
new Object[]{type, tClass, t});
}
if (tClass == NetworkListener.class && t instanceof NetworkListener) {
return processNetworkListener(type, (NetworkListener) t, events);
} else if (tClass == Http.class && t instanceof Http) {
return processProtocol(type, (Protocol) t.getParent(), events);
} else if (tClass == FileCache.class && t instanceof FileCache) {
return processProtocol(type, (Protocol) t.getParent().getParent(), null);
} else if (tClass == Ssl.class && t instanceof Ssl) {
/*
* Make sure the SSL parent is in fact a protocol. It could
* be a jmx-connector.
*/
final ConfigBeanProxy parent = t.getParent();
if (parent instanceof Protocol) {
return processProtocol(type, (Protocol) parent, null);
}
} else if (tClass == Protocol.class && t instanceof Protocol) {
return processProtocol(type, (Protocol) t, null);
} else if (tClass == ThreadPool.class && t instanceof ThreadPool) {
NotProcessed notProcessed = null;
for (NetworkListener listener : ((ThreadPool) t).findNetworkListeners()) {
notProcessed = processNetworkListener(type, listener, null);
}
return notProcessed;
} else if (tClass == Transport.class && t instanceof Transport) {
NotProcessed notProcessed = null;
for (NetworkListener listener : ((Transport) t).findNetworkListeners()) {
notProcessed = processNetworkListener(type, listener, null);
}
return notProcessed;
} else if (tClass == VirtualServer.class
&& t instanceof VirtualServer
&& !grizzlyService.hasMapperUpdateListener()) {
return processVirtualServer(type, (VirtualServer) t);
} else if (tClass == SystemProperty.class && t instanceof SystemProperty) {
NetworkConfig networkConfig = config.getNetworkConfig();
if ((networkConfig != null) && ((SystemProperty)t).getName().endsWith("LISTENER_PORT")) {
for (NetworkListener listener : networkConfig.getNetworkListeners().getNetworkListener()) {
if (listener.getPort().equals(((SystemProperty)t).getValue())) {
return processNetworkListener(Changed.TYPE.CHANGE, listener, events);
}
}
}
return null;
}
return null;
}
}, logger);
}
private <T extends ConfigBeanProxy> NotProcessed processNetworkListener(Changed.TYPE type,
NetworkListener listener, PropertyChangeEvent[] changedProperties) {
if (findConfigName(listener).equals(findConfigName(config))) {
boolean isAdminListener = ADMIN_LISTENER.equals(listener.getName());
Lock portLock = null;
try {
portLock = acquirePortLock(listener);
if (type == Changed.TYPE.ADD) {
final int[] ports = portLock.getPorts();
if (isAdminListener && ports[ports.length - 1] == -1) {
return null;
}
final Future future = grizzlyService.createNetworkProxy(listener);
if (future != null) {
future.get(RECONFIG_LOCK_TIMEOUT_SEC, TimeUnit.SECONDS);
grizzlyService.registerContainerAdapters();
} else {
logger.log(Level.FINE, "Skipping proxy registration for the listener {0}",
listener.getName());
}
} else if (type == Changed.TYPE.REMOVE) {
if (!isAdminListener) {
grizzlyService.removeNetworkProxy(listener);
}
} else if (type == Changed.TYPE.CHANGE) {
if (isAdminListener) {
final boolean dynamic = isAdminDynamic(changedProperties);
if (dynamic) {
GrizzlyProxy proxy = (GrizzlyProxy) grizzlyService.lookupNetworkProxy(listener);
if (proxy != null) {
GrizzlyListener netListener = proxy.getUnderlyingListener();
netListener.processDynamicConfigurationChange(
grizzlyService.getHabitat(), changedProperties);
return null;
}
}
return null;
}
// Restart the network listener
grizzlyService.restartNetworkListener(listener, RECONFIG_LOCK_TIMEOUT_SEC, TimeUnit.SECONDS);
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Network listener configuration error. Type: " + type, e);
} finally {
if (portLock != null) {
releaseListenerLock(portLock);
}
}
}
return null;
}
private String findConfigName(final ConfigBeanProxy child) {
ConfigBeanProxy bean = child;
while(bean != null && ! (bean instanceof Config)) {
bean = bean.getParent();
}
return bean != null ? ((Config) bean).getName() : "";
}
private boolean isAdminDynamic(PropertyChangeEvent[] events) {
if (events == null || events.length == 0) {
return false;
}
// for now, anything other than comet support will require a restart
for (PropertyChangeEvent e : events) {
if ("comet-support-enabled".equals(e.getPropertyName())) {
return true;
}
}
return false;
}
private NotProcessed processProtocol(Changed.TYPE type, Protocol protocol, PropertyChangeEvent[] events) {
NotProcessed notProcessed = null;
for (NetworkListener listener : protocol.findNetworkListeners()) {
notProcessed = processNetworkListener(type, listener, events);
}
return notProcessed;
}
private NotProcessed processVirtualServer(Changed.TYPE type, VirtualServer vs) {
NotProcessed notProcessed = null;
for (NetworkListener n : vs.findNetworkListeners()) {
notProcessed = processNetworkListener(type, n, null);
}
return notProcessed;
}
public void setGrizzlyService(GrizzlyService grizzlyService) {
this.grizzlyService = grizzlyService;
}
public Logger getLogger() {
return logger;
}
/**
* Lock TCP ports, which will take part in the reconfiguration to avoid collisions
*/
private Lock acquirePortLock(NetworkListener listener) throws InterruptedException, TimeoutException {
final boolean isLoggingFinest = logger.isLoggable(Level.FINEST);
final int port = getPort(listener);
try {
while (true) {
logger.finest("Aquire reconfig lock");
if (reconfigLock.tryLock(RECONFIG_LOCK_TIMEOUT_SEC, TimeUnit.SECONDS)) {
Future lock = reconfigByPortLock.get(port);
if (isLoggingFinest) {
logger.log(Level.FINEST, "Reconfig lock for port: {0} is {1}",
new Object[]{port, lock});
}
int proxyPort = -1;
if (lock == null) {
final NetworkProxy runningProxy = grizzlyService.lookupNetworkProxy(listener);
if (runningProxy != null) {
proxyPort = runningProxy.getPort();
if (port != proxyPort) {
lock = reconfigByPortLock.get(proxyPort);
if (isLoggingFinest) {
logger.log(Level.FINEST, "Reconfig lock for proxyport: {0} is {1}",
new Object[]{proxyPort, lock});
}
} else {
proxyPort = -1;
}
}
}
if (lock != null) {
reconfigLock.unlock();
try {
logger.finest("Waiting on reconfig lock");
lock.get(RECONFIG_LOCK_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw new IllegalStateException(e);
}
} else {
final FutureImpl future = SafeFutureImpl.create();
if (isLoggingFinest) {
logger.log(Level.FINEST, "Set reconfig lock for ports: {0} and {1}: {2}",
new Object[]{port, proxyPort, future});
}
reconfigByPortLock.put(port, future);
if (proxyPort != -1) {
reconfigByPortLock.put(proxyPort, future);
}
return new Lock(port, proxyPort);
}
} else {
throw new TimeoutException("Lock timeout");
}
}
} finally {
if (reconfigLock.isHeldByCurrentThread()) {
reconfigLock.unlock();
}
}
}
private void releaseListenerLock(Lock lock) {
final boolean isLoggingFinest = logger.isLoggable(Level.FINEST);
reconfigLock.lock();
try {
final int[] ports = lock.getPorts();
if (isLoggingFinest) {
logger.log(Level.FINEST, "Release reconfig lock for ports: {0}",
Arrays.toString(ports));
}
FutureImpl future = null;
for (int port : ports) {
if (port != -1) {
future = reconfigByPortLock.remove(port);
}
}
if (future != null) {
if (isLoggingFinest) {
logger.log(Level.FINEST, "Release reconfig lock, set result: {0}",
future);
}
future.result(new Result<Thread>(Thread.currentThread()));
}
} finally {
reconfigLock.unlock();
}
}
private int getPort(NetworkListener listener) {
int listenerPort = -1;
try {
listenerPort = Integer.parseInt(listener.getPort());
} catch (NumberFormatException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Can not parse network-listener port number: {0}",
listener.getPort());
}
}
return listenerPort;
}
private static final class Lock {
private final int[] ports;
public Lock(int... ports) {
this.ports = ports;
}
public int[] getPorts() {
return ports;
}
}
}