blob: 19b52584bc230aef5f550ddc4fad9629a66785ba [file] [log] [blame]
/*
* 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;
}
}