summaryrefslogtreecommitdiffstats
path: root/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceTracker.java
blob: fca85ee71e24e373f433a5f2cd72806dcb2e96bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
 *  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.tuscany.sca.binding.ws.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 (Object o : config.getServices().values()) {
            AxisService service = (AxisService)o;
            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();
        }
        // TODO: This is very bad, but AxisConfiguration has no removeObserver method!
        config.getObserversList().remove(observer);
        for (AxisService service : services) {
            listener.serviceRemoved(service);
        }
        services = null;
    }
}