blob: a6e4e4ef20c8a25cbf5ffbdc33d671a55ab2b023 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util.component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* An ContainerLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
* <p>
* Beans can be added the ContainerLifeCycle either as managed beans or as unmanaged beans. A managed bean is started, stopped and destroyed with the aggregate.
* An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
* <p>
* When a {@link LifeCycle} bean is added without a managed state being specified the state is determined heuristically:
* <ul>
* <li>If the added bean is running, it will be added as an unmanaged bean.
* <li>If the added bean is !running and the container is !running, it will be added as an AUTO bean (see below).
* <li>If the added bean is !running and the container is starting, it will be added as an managed bean and will be started (this handles the frequent case of
* new beans added during calls to doStart).
* <li>If the added bean is !running and the container is started, it will be added as an unmanaged bean.
* </ul>
* When the container is started, then all contained managed beans will also be started. Any contained Auto beans
* will be check for their status and if already started will be switched unmanaged beans, else they will be
* started and switched to managed beans. Beans added after a container is started are not started and their state needs to
* be explicitly managed.
* <p>
* When stopping the container, a contained bean will be stopped by this aggregate only if it
* is started by this aggregate.
* <p>
* The methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to
* explicitly control the life cycle relationship.
* <p>
* If adding a bean that is shared between multiple {@link ContainerLifeCycle} instances, then it should be started before being added, so it is unmanaged, or
* the API must be used to explicitly set it as unmanaged.
* <p>
* This class also provides utility methods to dump deep structures of objects. It the dump, the following symbols are used to indicate the type of contained object:
* <pre>
* SomeContainerLifeCycleInstance
* +- contained POJO instance
* += contained MANAGED object, started and stopped with this instance
* +~ referenced UNMANAGED object, with separate lifecycle
* +? referenced AUTO object that could become MANAGED or UNMANAGED.
* </pre>
*/
/* ------------------------------------------------------------ */
/**
*/
@ManagedObject("Implementation of Container and LifeCycle")
public class ContainerLifeCycle extends AbstractLifeCycle implements Container, Destroyable, Dumpable
{
private static final Logger LOG = Log.getLogger(ContainerLifeCycle.class);
private final List<Bean> _beans = new CopyOnWriteArrayList<>();
private final List<Container.Listener> _listeners = new CopyOnWriteArrayList<>();
private boolean _doStarted = false;
public ContainerLifeCycle()
{
}
/**
* Starts the managed lifecycle beans in the order they were added.
*/
@Override
protected void doStart() throws Exception
{
// indicate that we are started, so that addBean will start other beans added.
_doStarted = true;
// start our managed and auto beans
for (Bean b : _beans)
{
if (b._bean instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)b._bean;
switch(b._managed)
{
case MANAGED:
if (!l.isRunning())
start(l);
break;
case AUTO:
if (l.isRunning())
unmanage(b);
else
{
manage(b);
start(l);
}
break;
}
}
}
super.doStart();
}
/**
* Starts the given lifecycle.
*
* @param l
* @throws Exception
*/
protected void start(LifeCycle l) throws Exception
{
l.start();
}
/**
* Stops the given lifecycle.
*
* @param l
* @throws Exception
*/
protected void stop(LifeCycle l) throws Exception
{
l.stop();
}
/**
* Stops the managed lifecycle beans in the reverse order they were added.
*/
@Override
protected void doStop() throws Exception
{
_doStarted = false;
super.doStop();
List<Bean> reverse = new ArrayList<>(_beans);
Collections.reverse(reverse);
for (Bean b : reverse)
{
if (b._managed==Managed.MANAGED && b._bean instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)b._bean;
if (l.isRunning())
stop(l);
}
}
}
/**
* Destroys the managed Destroyable beans in the reverse order they were added.
*/
@Override
public void destroy()
{
List<Bean> reverse = new ArrayList<>(_beans);
Collections.reverse(reverse);
for (Bean b : reverse)
{
if (b._bean instanceof Destroyable && (b._managed==Managed.MANAGED || b._managed==Managed.POJO))
{
Destroyable d = (Destroyable)b._bean;
d.destroy();
}
}
_beans.clear();
}
/**
* @param bean the bean to test
* @return whether this aggregate contains the bean
*/
public boolean contains(Object bean)
{
for (Bean b : _beans)
if (b._bean == bean)
return true;
return false;
}
/**
* @param bean the bean to test
* @return whether this aggregate contains and manages the bean
*/
public boolean isManaged(Object bean)
{
for (Bean b : _beans)
if (b._bean == bean)
return b.isManaged();
return false;
}
/**
* Adds the given bean, detecting whether to manage it or not.
* If the bean is a {@link LifeCycle}, then it will be managed if it is not
* already started and not managed if it is already started.
* The {@link #addBean(Object, boolean)}
* method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
* methods may be used after an add to change the status.
*
* @param o the bean object to add
* @return true if the bean was added, false if it was already present
*/
@Override
public boolean addBean(Object o)
{
if (o instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)o;
return addBean(o,l.isRunning()?Managed.UNMANAGED:Managed.AUTO);
}
return addBean(o,Managed.POJO);
}
/**
* Adds the given bean, explicitly managing it or not.
*
* @param o The bean object to add
* @param managed whether to managed the lifecycle of the bean
* @return true if the bean was added, false if it was already present
*/
public boolean addBean(Object o, boolean managed)
{
if (o instanceof LifeCycle)
return addBean(o,managed?Managed.MANAGED:Managed.UNMANAGED);
return addBean(o,managed?Managed.POJO:Managed.UNMANAGED);
}
public boolean addBean(Object o, Managed managed)
{
if (contains(o))
return false;
Bean new_bean = new Bean(o);
// if the bean is a Listener
if (o instanceof Container.Listener)
addEventListener((Container.Listener)o);
// Add the bean
_beans.add(new_bean);
// Tell existing listeners about the new bean
for (Container.Listener l:_listeners)
l.beanAdded(this,o);
try
{
switch (managed)
{
case UNMANAGED:
unmanage(new_bean);
break;
case MANAGED:
manage(new_bean);
if (isStarting() && _doStarted)
{
LifeCycle l = (LifeCycle)o;
if (!l.isRunning())
start(l);
}
break;
case AUTO:
if (o instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)o;
if (isStarting())
{
if (l.isRunning())
unmanage(new_bean);
else if (_doStarted)
{
manage(new_bean);
start(l);
}
else
new_bean._managed=Managed.AUTO;
}
else if (isStarted())
unmanage(new_bean);
else
new_bean._managed=Managed.AUTO;
}
else
new_bean._managed=Managed.POJO;
break;
case POJO:
new_bean._managed=Managed.POJO;
}
}
catch (RuntimeException | Error e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
if (LOG.isDebugEnabled())
LOG.debug("{} added {}",this,new_bean);
return true;
}
/* ------------------------------------------------------------ */
/** Add a managed lifecycle.
* <p>This is a convenience method that uses addBean(lifecycle,true)
* and then ensures that the added bean is started iff this container
* is running. Exception from nested calls to start are caught and
* wrapped as RuntimeExceptions
* @param lifecycle
*/
public void addManaged(LifeCycle lifecycle)
{
addBean(lifecycle,true);
try
{
if (isRunning() && !lifecycle.isRunning())
start(lifecycle);
}
catch (RuntimeException | Error e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
@Override
public void addEventListener(Container.Listener listener)
{
if (_listeners.contains(listener))
return;
_listeners.add(listener);
// tell it about existing beans
for (Bean b:_beans)
{
listener.beanAdded(this,b._bean);
// handle inheritance
if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
{
if (b._bean instanceof ContainerLifeCycle)
((ContainerLifeCycle)b._bean).addBean(listener, false);
else
((Container)b._bean).addBean(listener);
}
}
}
/**
* Manages a bean already contained by this aggregate, so that it is started/stopped/destroyed with this
* aggregate.
*
* @param bean The bean to manage (must already have been added).
*/
public void manage(Object bean)
{
for (Bean b : _beans)
{
if (b._bean == bean)
{
manage(b);
return;
}
}
throw new IllegalArgumentException("Unknown bean " + bean);
}
private void manage(Bean bean)
{
if (bean._managed!=Managed.MANAGED)
{
bean._managed=Managed.MANAGED;
if (bean._bean instanceof Container)
{
for (Container.Listener l:_listeners)
{
if (l instanceof InheritedListener)
{
if (bean._bean instanceof ContainerLifeCycle)
((ContainerLifeCycle)bean._bean).addBean(l,false);
else
((Container)bean._bean).addBean(l);
}
}
}
if (bean._bean instanceof AbstractLifeCycle)
{
((AbstractLifeCycle)bean._bean).setStopTimeout(getStopTimeout());
}
}
}
/**
* Unmanages a bean already contained by this aggregate, so that it is not started/stopped/destroyed with this
* aggregate.
*
* @param bean The bean to unmanage (must already have been added).
*/
public void unmanage(Object bean)
{
for (Bean b : _beans)
{
if (b._bean == bean)
{
unmanage(b);
return;
}
}
throw new IllegalArgumentException("Unknown bean " + bean);
}
private void unmanage(Bean bean)
{
if (bean._managed!=Managed.UNMANAGED)
{
if (bean._managed==Managed.MANAGED && bean._bean instanceof Container)
{
for (Container.Listener l:_listeners)
{
if (l instanceof InheritedListener)
((Container)bean._bean).removeBean(l);
}
}
bean._managed=Managed.UNMANAGED;
}
}
@Override
public Collection<Object> getBeans()
{
return getBeans(Object.class);
}
public void setBeans(Collection<Object> beans)
{
for (Object bean : beans)
addBean(bean);
}
@Override
public <T> Collection<T> getBeans(Class<T> clazz)
{
ArrayList<T> beans = new ArrayList<>();
for (Bean b : _beans)
{
if (clazz.isInstance(b._bean))
beans.add(clazz.cast(b._bean));
}
return beans;
}
@Override
public <T> T getBean(Class<T> clazz)
{
for (Bean b : _beans)
{
if (clazz.isInstance(b._bean))
return clazz.cast(b._bean);
}
return null;
}
/**
* Removes all bean
*/
public void removeBeans()
{
ArrayList<Bean> beans= new ArrayList<>(_beans);
for (Bean b : beans)
remove(b);
}
private Bean getBean(Object o)
{
for (Bean b : _beans)
{
if (b._bean == o)
return b;
}
return null;
}
@Override
public boolean removeBean(Object o)
{
Bean b=getBean(o);
return b!=null && remove(b);
}
private boolean remove(Bean bean)
{
if (_beans.remove(bean))
{
boolean wasManaged = bean.isManaged();
unmanage(bean);
for (Container.Listener l:_listeners)
l.beanRemoved(this,bean._bean);
if (bean._bean instanceof Container.Listener)
removeEventListener((Container.Listener)bean._bean);
// stop managed beans
if (wasManaged && bean._bean instanceof LifeCycle)
{
try
{
stop((LifeCycle)bean._bean);
}
catch(RuntimeException | Error e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
return true;
}
return false;
}
@Override
public void removeEventListener(Container.Listener listener)
{
if (_listeners.remove(listener))
{
// remove existing beans
for (Bean b:_beans)
{
listener.beanRemoved(this,b._bean);
if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
((Container)b._bean).removeBean(listener);
}
}
}
@Override
public void setStopTimeout(long stopTimeout)
{
super.setStopTimeout(stopTimeout);
for (Bean bean : _beans)
{
if (bean.isManaged() && bean._bean instanceof AbstractLifeCycle)
((AbstractLifeCycle)bean._bean).setStopTimeout(stopTimeout);
}
}
/**
* Dumps to {@link System#err}.
* @see #dump()
*/
@ManagedOperation("Dump the object to stderr")
public void dumpStdErr()
{
try
{
dump(System.err, "");
}
catch (IOException e)
{
LOG.warn(e);
}
}
@Override
@ManagedOperation("Dump the object to a string")
public String dump()
{
return dump(this);
}
public static String dump(Dumpable dumpable)
{
StringBuilder b = new StringBuilder();
try
{
dumpable.dump(b, "");
}
catch (IOException e)
{
LOG.warn(e);
}
return b.toString();
}
public void dump(Appendable out) throws IOException
{
dump(out, "");
}
protected void dumpThis(Appendable out) throws IOException
{
out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
}
public static void dumpObject(Appendable out, Object o) throws IOException
{
try
{
if (o instanceof LifeCycle)
out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
else
out.append(String.valueOf(o)).append("\n");
}
catch (Throwable th)
{
out.append(" => ").append(th.toString()).append('\n');
}
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpBeans(out,indent);
}
protected void dumpBeans(Appendable out, String indent, Collection<?>... collections) throws IOException
{
dumpThis(out);
int size = _beans.size();
for (Collection<?> c : collections)
size += c.size();
if (size == 0)
return;
int i = 0;
for (Bean b : _beans)
{
i++;
switch(b._managed)
{
case POJO:
out.append(indent).append(" +- ");
if (b._bean instanceof Dumpable)
((Dumpable)b._bean).dump(out, indent + (i == size ? " " : " | "));
else
dumpObject(out, b._bean);
break;
case MANAGED:
out.append(indent).append(" += ");
if (b._bean instanceof Dumpable)
((Dumpable)b._bean).dump(out, indent + (i == size ? " " : " | "));
else
dumpObject(out, b._bean);
break;
case UNMANAGED:
out.append(indent).append(" +~ ");
dumpObject(out, b._bean);
break;
case AUTO:
out.append(indent).append(" +? ");
if (b._bean instanceof Dumpable)
((Dumpable)b._bean).dump(out, indent + (i == size ? " " : " | "));
else
dumpObject(out, b._bean);
break;
}
}
if (i<size)
out.append(indent).append(" |\n");
for (Collection<?> c : collections)
{
for (Object o : c)
{
i++;
out.append(indent).append(" +> ");
if (o instanceof Dumpable)
((Dumpable)o).dump(out, indent + (i == size ? " " : " | "));
else
dumpObject(out, o);
}
}
}
public static void dump(Appendable out, String indent, Collection<?>... collections) throws IOException
{
if (collections.length == 0)
return;
int size = 0;
for (Collection<?> c : collections)
size += c.size();
if (size == 0)
return;
int i = 0;
for (Collection<?> c : collections)
{
for (Object o : c)
{
i++;
out.append(indent).append(" +- ");
if (o instanceof Dumpable)
((Dumpable)o).dump(out, indent + (i == size ? " " : " | "));
else
dumpObject(out, o);
}
}
}
enum Managed { POJO, MANAGED, UNMANAGED, AUTO };
private static class Bean
{
private final Object _bean;
private volatile Managed _managed = Managed.POJO;
private Bean(Object b)
{
_bean = b;
}
public boolean isManaged()
{
return _managed==Managed.MANAGED;
}
@Override
public String toString()
{
return String.format("{%s,%s}", _bean, _managed);
}
}
public void updateBean(Object oldBean, final Object newBean)
{
if (newBean!=oldBean)
{
if (oldBean!=null)
removeBean(oldBean);
if (newBean!=null)
addBean(newBean);
}
}
public void updateBeans(Object[] oldBeans, final Object[] newBeans)
{
// remove oldChildren not in newChildren
if (oldBeans!=null)
{
loop: for (Object o:oldBeans)
{
if (newBeans!=null)
{
for (Object n:newBeans)
if (o==n)
continue loop;
}
removeBean(o);
}
}
// add new beans not in old
if (newBeans!=null)
{
loop: for (Object n:newBeans)
{
if (oldBeans!=null)
{
for (Object o:oldBeans)
if (o==n)
continue loop;
}
addBean(n);
}
}
}
}