| // |
| // ======================================================================== |
| // Copyright (c) 1995-2016 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 conveniance 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); |
| } |
| } |
| } |
| } |