/* * 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.axis2.transport.base.tracker; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Set; import org.apache.axiom.om.OMElement; import org.apache.axis2.AxisFault; import org.apache.axis2.description.AxisModule; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.AxisServiceGroup; import org.apache.axis2.description.Parameter; import org.apache.axis2.engine.AxisConfiguration; import org.apache.axis2.engine.AxisEvent; import org.apache.axis2.engine.AxisObserver; /** * <p>Tracks services deployed in a given {@link AxisConfiguration}. * The tracker is configured with references to three objects:</p> * <ol> * <li>An {@link AxisConfiguration} to watch.</li> * <li>An {@link AxisServiceFilter} restricting the services to track.</li> * <li>An {@link AxisServiceTrackerListener} receiving tracking events.</li> * </ol> * <p>An instance of this class maintains an up-to-date list of services * satisfying all of the following criteria:</p> * <ol> * <li>The service is deployed in the given {@link AxisConfiguration}.</li> * <li>The service is started, i.e. {@link AxisService#isActive()} returns true.</li> * <li>The service matches the criteria specified by the given * {@link AxisServiceFilter} instance.</li> * </ol> * <p>Whenever a service appears on the list, the tracker will call * {@link AxisServiceTrackerListener#serviceAdded(AxisService)}. When a service disappears, it * will call {@link AxisServiceTrackerListener#serviceRemoved(AxisService)}.</p> * <p>When the tracker is created, it is initially in the stopped state. In this state no * events will be sent to the listener. It can be started using {@link #start()} and stopped again * using {@link #stop()}. The tracker list is defined to be empty when the tracker is in the * stopped state. This implies that a call to {@link #start()} will generate * {@link AxisServiceTrackerListener#serviceAdded(AxisService)} events for all services that meet * the above criteria at that point in time. In the same way, {@link #stop()} will generate * {@link AxisServiceTrackerListener#serviceRemoved(AxisService)} events for the current entries * in the list.</p> * <p>As a corollary the tracker guarantees that during a complete lifecycle (start-stop), * there will be exactly one {@link AxisServiceTrackerListener#serviceRemoved(AxisService)} event * for every {@link AxisServiceTrackerListener#serviceAdded(AxisService)} event and vice-versa. * This property is important when the tracker is used to allocate resources for a dynamic set * of services.</p> * * <h2>Limitations</h2> * * <p>The tracker is not able to detect property changes on services. E.g. if a service initially * matches the filter criteria, but later changes so that it doesn't match the criteria any more, * the tracker will not be able to detect this and the service will not be removed from the tracker * list.</p> */ public class AxisServiceTracker { private final AxisObserver observer = new AxisObserver() { public void init(AxisConfiguration axisConfig) {} public void serviceUpdate(AxisEvent event, final AxisService service) { switch (event.getEventType()) { case AxisEvent.SERVICE_DEPLOY: case AxisEvent.SERVICE_START: if (filter.matches(service)) { boolean pending; synchronized (lock) { if (pending = (pendingActions != null)) { pendingActions.add(new Runnable() { public void run() { serviceAdded(service); } }); } } if (!pending) { serviceAdded(service); } } break; case AxisEvent.SERVICE_REMOVE: case AxisEvent.SERVICE_STOP: // Don't check filter here because the properties of the service may have // changed in the meantime. boolean pending; synchronized (lock) { if (pending = (pendingActions != null)) { pendingActions.add(new Runnable() { public void run() { serviceRemoved(service); } }); } } if (!pending) { serviceRemoved(service); } } } public void moduleUpdate(AxisEvent event, AxisModule module) {} public void addParameter(Parameter param) throws AxisFault {} public void removeParameter(Parameter param) throws AxisFault {} public void deserializeParameters(OMElement parameterElement) throws AxisFault {} public Parameter getParameter(String name) { return null; } public ArrayList<Parameter> getParameters() { return null; } public boolean isParameterLocked(String parameterName) { return false; } public void serviceGroupUpdate(AxisEvent event, AxisServiceGroup serviceGroup) {} }; private final AxisConfiguration config; final AxisServiceFilter filter; private final AxisServiceTrackerListener listener; /** * Object used to synchronize access to {@link #pendingActions} and {@link #services}. */ final Object lock = new Object(); /** * Queue for notifications received by the {@link AxisObserver} during startup of the tracker. * We need this because the events may already be reflected in the list of services returned * by {@link AxisConfiguration#getServices()} (getting the list of currently deployed services * and adding the observer can't be done atomically). It also allows us to make sure that * events are sent to the listener in the right order, e.g. when a service is being removed * during startup of the tracker. */ Queue<Runnable> pendingActions; /** * The current list of services. <code>null</code> if the tracker is stopped. */ private Set<AxisService> services; public AxisServiceTracker(AxisConfiguration config, AxisServiceFilter filter, AxisServiceTrackerListener listener) { this.config = config; this.filter = filter; this.listener = listener; } /** * Check whether the tracker is started. * * @return <code>true</code> if the tracker is started */ public boolean isStarted() { return services != null; } /** * Start the tracker. * * @throws IllegalStateException if the tracker has already been started */ public void start() { if (services != null) { throw new IllegalStateException(); } synchronized (lock) { pendingActions = new LinkedList<Runnable>(); config.addObservers(observer); services = new HashSet<AxisService>(); } for (AxisService service : config.getServices().values()) { if (service.isActive() && filter.matches(service)) { serviceAdded(service); } } while (true) { Runnable action; synchronized (lock) { action = pendingActions.poll(); if (action == null) { pendingActions = null; break; } } action.run(); } } void serviceAdded(AxisService service) { // callListener may be false because the observer got an event for a service that // was already in the initial list of services retrieved by AxisConfiguration#getServices. boolean callListener; synchronized (lock) { callListener = services.add(service); } if (callListener) { listener.serviceAdded(service); } } void serviceRemoved(AxisService service) { // callListener may be false because the observer invokes this method without applying the // filter. boolean callListener; synchronized (lock) { callListener = services.remove(service); } if (callListener) { listener.serviceRemoved(service); } } /** * Stop the tracker. * * @throws IllegalStateException if the tracker is not started */ public void stop() { if (services == null) { throw new IllegalStateException(); } config.removeObserver(observer); for (AxisService service : services) { listener.serviceRemoved(service); } services = null; } }