/*
* 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.tomee.catalina;
import org.apache.catalina.Container;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Service;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.openejb.loader.SystemInstance;
import org.apache.tomee.catalina.cluster.TomEEClusterListener;
import org.apache.tomee.catalina.remote.TomEERemoteWebapp;
import org.apache.tomee.loader.TomcatHelper;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Observers events from Tomcat to configure
* web applications etc.
*
* @version $Rev$ $Date$
*/
public class GlobalListenerSupport implements PropertyChangeListener, LifecycleListener {
private static final boolean REMOTE_SUPPORT = SystemInstance.get().getOptions().get("tomee.remote.support", true);
/**
* The LifecycleEvent type for the "component init" event.
* Tomcat 6.0.x only
* Removed in Tomcat 7
*/
public static final String INIT_EVENT = "init";
/**
* The LifecycleEvent type for the "component destroy" event.
* Tomcat 6.0.x only
* Removed in Tomcat 7
*/
public static final String DESTROY_EVENT = "destroy";
/**
* Tomcat server instance
*/
private final StandardServer standardServer;
/**
* Listener for context, host, server operations
*/
private final ContextListener contextListener;
/**
* Creates a new instance.
*
* @param standardServer tomcat server instance
* @param contextListener context listener instance
*/
public GlobalListenerSupport(final StandardServer standardServer, final ContextListener contextListener) {
if (standardServer == null) {
throw new NullPointerException("standardServer is null");
}
if (contextListener == null) {
throw new NullPointerException("contextListener is null");
}
this.standardServer = standardServer;
this.contextListener = contextListener; // this.contextListener is now an instance of TomcatWebAppBuilder
}
/**
* {@inheritDoc}
*/
public void lifecycleEvent(final LifecycleEvent event) {
final Object source = event.getSource();
if (source instanceof StandardContext) {
final StandardContext standardContext = (StandardContext) source;
if (standardContext instanceof IgnoredStandardContext) {
return;
}
final String type = event.getType();
if (INIT_EVENT.equals(type) || Lifecycle.BEFORE_INIT_EVENT.equals(type)) {
contextListener.init(standardContext);
} else if (Lifecycle.BEFORE_START_EVENT.equals(type)) {
contextListener.beforeStart(standardContext);
} else if (Lifecycle.BEFORE_START_EVENT.equals(type)) {
contextListener.beforeStart(standardContext);
} else if (Lifecycle.START_EVENT.equals(type)) {
if (TomcatHelper.isTomcat7()) {
standardContext.addParameter("openejb.start.late", "true");
}
contextListener.start(standardContext);
} else if (Lifecycle.AFTER_START_EVENT.equals(type)) {
contextListener.afterStart(standardContext);
if (TomcatHelper.isTomcat7()) {
standardContext.removeParameter("openejb.start.late");
}
} else if (Lifecycle.BEFORE_STOP_EVENT.equals(type)) {
contextListener.beforeStop(standardContext);
} else if (Lifecycle.STOP_EVENT.equals(type)) {
contextListener.stop(standardContext);
} else if (Lifecycle.AFTER_STOP_EVENT.equals(type)) {
contextListener.afterStop(standardContext);
} else if (DESTROY_EVENT.equals(type) || Lifecycle.AFTER_DESTROY_EVENT.equals(type)) {
contextListener.destroy(standardContext);
} else if (Lifecycle.CONFIGURE_START_EVENT.equals(type)) {
contextListener.configureStart(standardContext);
}
} else if (StandardHost.class.isInstance(source)) {
final StandardHost standardHost = (StandardHost) source;
final String type = event.getType();
if (Lifecycle.PERIODIC_EVENT.equals(type)) {
contextListener.checkHost(standardHost);
} else if (Lifecycle.AFTER_START_EVENT.equals(type) && REMOTE_SUPPORT) {
final TomEERemoteWebapp child = new TomEERemoteWebapp();
if (!hasChild(standardHost, child.getName())) {
standardHost.addChild(child);
} // else old tomee webapp surely
}
} else if (StandardServer.class.isInstance(source)) {
final StandardServer standardServer = (StandardServer) source;
final String type = event.getType();
if (Lifecycle.START_EVENT.equals(type)) {
contextListener.start(standardServer);
}
if (Lifecycle.BEFORE_STOP_EVENT.equals(type)) {
TomcatHelper.setStopping(true);
TomEEClusterListener.stop();
}
if (Lifecycle.AFTER_STOP_EVENT.equals(type)) {
contextListener.afterStop(standardServer);
}
}
}
private static boolean hasChild(final StandardHost host, final String name) {
for (final Container child : host.findChildren()) {
// the TomEERemoteWebapp path = "/" + name
if (name.equals(child.getName())
|| (StandardContext.class.isInstance(child) && ("/" + name).equals(StandardContext.class.cast(child).getPath()))) {
return true;
}
}
return false;
}
/**
* Starts operation.
*/
public void start() {
// hook the hosts so we get notified before contexts are started
standardServer.addPropertyChangeListener(this);
standardServer.addLifecycleListener(this);
for (final Service service : standardServer.findServices()) {
serviceAdded(service);
}
}
/**
* Stops operation.
*/
public void stop() {
standardServer.removePropertyChangeListener(this);
}
/**
* Service is added.
*
* @param service tomcat service
*/
private void serviceAdded(final Service service) {
final Container container = service.getContainer();
if (container instanceof StandardEngine) {
final StandardEngine engine = (StandardEngine) container;
engineAdded(engine);
}
}
/**
* Service removed.
*
* @param service tomcat service
*/
private void serviceRemoved(final Service service) {
final Container container = service.getContainer();
if (container instanceof StandardEngine) {
final StandardEngine engine = (StandardEngine) container;
engineRemoved(engine);
}
}
/**
* Engine is added.
*
* @param engine tomcat engine
*/
private void engineAdded(final StandardEngine engine) {
addContextListener(engine);
for (final Container child : engine.findChildren()) {
if (child instanceof StandardHost) {
final StandardHost host = (StandardHost) child;
hostAdded(host);
}
}
}
/**
* Engine is removed.
*
* @param engine tomcat engine
*/
private void engineRemoved(final StandardEngine engine) {
for (final Container child : engine.findChildren()) {
if (child instanceof StandardHost) {
final StandardHost host = (StandardHost) child;
hostRemoved(host);
}
}
}
/**
* Host is added.
*
* @param host tomcat host.
*/
private void hostAdded(final StandardHost host) {
addContextListener(host);
host.addLifecycleListener(this);
for (final Container child : host.findChildren()) {
if (child instanceof StandardContext) {
final StandardContext context = (StandardContext) child;
contextAdded(context);
}
}
}
/**
* Host is removed.
*
* @param host tomcat host
*/
private void hostRemoved(final StandardHost host) {
for (final Container child : host.findChildren()) {
if (child instanceof StandardContext) {
final StandardContext context = (StandardContext) child;
contextRemoved(context);
}
}
}
/**
* New context is added.
*
* @param context tomcat context
*/
private void contextAdded(final StandardContext context) {
// put this class as the first listener so we can process the application before any classes are loaded
forceFirstLifecycleListener(context);
}
/**
* Update context lifecycle listeners.
*
* @param context tomcat context.
*/
private void forceFirstLifecycleListener(final StandardContext context) {
final LifecycleListener[] listeners = context.findLifecycleListeners();
// if we are already first return
if (listeners.length > 0 && listeners[0] == this) {
return;
}
// remove all of the current listeners
for (final LifecycleListener listener : listeners) {
context.removeLifecycleListener(listener);
}
// add this class (as first)
context.addLifecycleListener(this);
// add back all listeners
for (final LifecycleListener listener : listeners) {
if (listener != this) {
context.addLifecycleListener(listener);
}
}
}
/**
* Context is removed.
*
* @param context tomcat context
*/
@SuppressWarnings({"UnusedDeclaration", "PMD.UnusedFormalParameter"})
private void contextRemoved(final StandardContext context) {
// TODO what to do?
}
/**
* {@inheritDoc}
*/
public void propertyChange(final PropertyChangeEvent event) {
if ("service".equals(event.getPropertyName())) {
final Object oldValue = event.getOldValue();
final Object newValue = event.getNewValue();
if (oldValue == null && newValue instanceof Service) {
serviceAdded((Service) newValue);
}
if (oldValue instanceof Service && newValue == null) {
serviceRemoved((Service) oldValue);
}
}
if ("children".equals(event.getPropertyName())) {
final Object source = event.getSource();
final Object oldValue = event.getOldValue();
final Object newValue = event.getNewValue();
if (source instanceof StandardEngine) {
if (oldValue == null && newValue instanceof StandardHost) {
hostAdded((StandardHost) newValue);
}
if (oldValue instanceof StandardHost && newValue == null) {
hostRemoved((StandardHost) oldValue);
}
}
if (source instanceof StandardHost) {
if (oldValue == null && newValue instanceof StandardContext) {
contextAdded((StandardContext) newValue);
}
if (oldValue instanceof StandardContext && newValue == null) {
contextRemoved((StandardContext) oldValue);
}
}
}
}
/**
* Setting monitoreable child field.
*
* @param containerBase host or engine
*/
@SuppressWarnings("unchecked")
private void addContextListener(final ContainerBase containerBase) {
boolean accessible = false;
Field field = null;
try {
field = ContainerBase.class.getDeclaredField("children");
accessible = field.isAccessible();
field.setAccessible(true);
Map<Object, Object> children = (Map<Object, Object>) field.get(containerBase);
if (children instanceof GlobalListenerSupport.MoniterableHashMap) {
return;
}
children = new GlobalListenerSupport.MoniterableHashMap(children, containerBase, "children", this);
field.set(containerBase, children);
} catch (final Exception e) {
e.printStackTrace();
} finally {
if (field != null) {
if (!accessible) {
field.setAccessible(false);
}
}
}
}
//Hashmap for monitoring children of engine and host, linked because:
// 1) deterministic, 2) avoid to handle the prop in application.xml
public static class MoniterableHashMap extends LinkedHashMap<Object, Object> {
private final Object source;
private final String propertyName;
private final PropertyChangeListener listener;
public MoniterableHashMap(final Map<Object, Object> m, final Object source, final String propertyName, final PropertyChangeListener listener) {
super(m);
this.source = source;
this.propertyName = propertyName;
this.listener = listener;
}
public Object put(final Object key, final Object value) {
final Object oldValue = super.put(key, value);
final PropertyChangeEvent event = new PropertyChangeEvent(source, propertyName, null, value);
listener.propertyChange(event);
return oldValue;
}
public Object remove(final Object key) {
final Object value = super.remove(key);
final PropertyChangeEvent event = new PropertyChangeEvent(source, propertyName, value, null);
listener.propertyChange(event);
return value;
}
}
}