1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 1999-2004 (C) Exoffice Technologies Inc. All Rights Reserved.
42 */
43
44 package org.exolab.jms.service;
45
46 import java.beans.BeanInfo;
47 import java.beans.IntrospectionException;
48 import java.beans.Introspector;
49 import java.beans.PropertyDescriptor;
50 import java.lang.reflect.Constructor;
51 import java.lang.reflect.InvocationTargetException;
52 import java.lang.reflect.Method;
53 import java.util.ArrayList;
54 import java.util.HashMap;
55 import java.util.Iterator;
56 import java.util.LinkedList;
57 import java.util.List;
58 import java.util.Map;
59
60
61 /***
62 * Default implementation of the {@link Services} interface.
63 *
64 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
65 * @author <a href="mailto:jima@comware.com.au">Jim Alateras</a>
66 * @version $Revision: 1.3 $ $Date: 2005/08/31 00:42:17 $
67 * @see Service
68 * @see Serviceable
69 */
70 public class ServiceManager extends Service implements Services {
71
72 /***
73 * A map of service type -> service instance (i.e, Class -> Object)
74 * representing the set of services. The service instance may be null,
75 * indicating that the service has been registered but not yet created.
76 */
77 private final Map _services = new HashMap();
78
79 /***
80 * A list of the service types in the order that they were registered. This
81 * is used by {@link #doStart} to resolve services in the order that they
82 * were registered with this.
83 */
84 private final List _addOrder = new ArrayList();
85
86 /***
87 * A list of service typs in the order that they were created. This is used
88 * to ensure dependent services are started in the correct order.
89 */
90 private final List _createOrder = new ArrayList();
91
92
93 /***
94 * Construct a new <code>ServiceManager</code>.
95 */
96 public ServiceManager() {
97 _services.put(Services.class, this);
98 }
99
100 /***
101 * Add a service of the specified type.
102 * <p/>
103 * The service will be constructed when it is first accessed via {@link
104 * #getService}.
105 *
106 * @param type the type of the service
107 * @throws ServiceAlreadyExistsException if the service already exists
108 * @throws ServiceException for any service error
109 */
110 public synchronized void addService(Class type) throws ServiceException {
111 if (type == null) {
112 throw new IllegalArgumentException("Argument 'type' is null");
113 }
114 checkExists(type);
115 _services.put(type, null);
116 _addOrder.add(type);
117 }
118
119 /***
120 * Add a service instance.
121 *
122 * @param service the service instance
123 * @throws ServiceAlreadyExistsException if the service already exists
124 * @throws ServiceException for any service error
125 */
126 public void addService(Object service) throws ServiceException {
127 if (service == null) {
128 throw new IllegalArgumentException("Argument 'service' is null");
129 }
130 Class type = service.getClass();
131 checkExists(type);
132 _services.put(type, service);
133 _addOrder.add(type);
134 }
135
136 /***
137 * Returns a service given its type.
138 * <p/>
139 * If the service has been registered but not constructed, it will be
140 * created and any setters populated.
141 *
142 * @param type the type of the service
143 * @return an instance of <code>type</code>
144 * @throws ServiceDoesNotExistException if the service doesn't exist, or is
145 * dependent on a service which doesn't
146 * exist
147 * @throws ServiceException for any service error
148 */
149 public synchronized Object getService(Class type) throws ServiceException {
150 LinkedList creating = new LinkedList();
151 List created = new ArrayList();
152 Object service = getService(type, creating, created);
153 Iterator iterator = created.iterator();
154 while (iterator.hasNext()) {
155 invokeSetters(iterator.next());
156 }
157 return service;
158 }
159
160 /***
161 * Start the service.
162 *
163 * @throws ServiceException if the service fails to start, or is already
164 * running
165 */
166 protected void doStart() throws ServiceException {
167 for (Iterator iter = _addOrder.iterator(); iter.hasNext();) {
168 Class type = (Class) iter.next();
169 getService(type);
170 }
171
172 for (Iterator iter = _createOrder.iterator(); iter.hasNext();) {
173 Class type = (Class) iter.next();
174 Object service = getService(type);
175 if (service instanceof Serviceable) {
176 ((Serviceable) service).start();
177 }
178 }
179 }
180
181 /***
182 * Stop the service.
183 *
184 * @throws ServiceException if the service fails to stop, or is already
185 * stopped
186 */
187 protected void doStop() throws ServiceException {
188 for (Iterator iter = _createOrder.iterator(); iter.hasNext();) {
189 Class type = (Class) iter.next();
190 Object service = getService(type);
191 if (service instanceof Serviceable) {
192 ((Serviceable) service).stop();
193 }
194 }
195 }
196
197 /***
198 * Returns a service given its type, creating it if required.
199 *
200 * @param type the type of the service
201 * @param creating the set of services currently being created
202 * @param created the set of services already created
203 * @return the service corresponding to <code>type<code>
204 * @throws ServiceDoesNotExistException if no service matches <code>type</code>
205 * @throws ServiceException for any service error
206 */
207 private Object getService(Class type, LinkedList creating, List created)
208 throws ServiceException {
209 Iterator types = _services.keySet().iterator();
210 List matches = new ArrayList();
211 while (types.hasNext()) {
212 Class clazz = (Class) types.next();
213 if (type.isAssignableFrom(clazz)) {
214 matches.add(clazz);
215 }
216 }
217 if (matches.isEmpty()) {
218 String msg = "Service of type " + type.getName()
219 + " not registered";
220 Class requiredBy = null;
221 if (!creating.isEmpty()) {
222 requiredBy = (Class) creating.getLast();
223 msg += ", but required by " + requiredBy.getName();
224 }
225 throw new ServiceDoesNotExistException(msg);
226 } else if (matches.size() > 1) {
227 throw new ServiceException(
228 "Multiple services match service type " + type.getName());
229 }
230 Class match = (Class) matches.get(0);
231 Object service = _services.get(match);
232 if (service == null) {
233 service = createService(match, creating, created);
234 _services.put(match, service);
235 _createOrder.add(match);
236 }
237 return service;
238 }
239
240 /***
241 * Create a new service given its type.
242 *
243 * @param type the service type
244 * @param creating the set of services currently being created
245 * @param created the set of services already created
246 * @return the service corresponding to <code>type<code>
247 * @throws ServiceException if the service can't be constructed
248 */
249 protected Object createService(Class type, LinkedList creating,
250 List created) throws ServiceException {
251 if (creating.contains(type)) {
252 throw new ServiceException("Circular dependency trying to construct "
253 + type.getName() + ": " + creating);
254 }
255 Object service;
256 Constructor[] constructors = type.getConstructors();
257 if (constructors.length > 1) {
258 throw new ServiceException("Cannot create service of type "
259 + type.getName()
260 + ": multiple public constructors");
261 } else if (constructors.length != 1) {
262 throw new ServiceException("Cannot create service of type "
263 + type.getName()
264 + ": no public constructor");
265 }
266 Constructor ctor = constructors[0];
267 Class[] types = ctor.getParameterTypes();
268 Object[] args = new Object[types.length];
269 try {
270 creating.add(type);
271 for (int i = 0; i < types.length; ++i) {
272 args[i] = getService(types[i], creating, created);
273 }
274 service = ctor.newInstance(args);
275 created.add(service);
276 } catch (IllegalAccessException exception) {
277 throw new ServiceException(
278 "Failed to create service of type: " + type,
279 exception);
280 } catch (InvocationTargetException exception) {
281 Throwable target = exception.getTargetException();
282 if (target == null) {
283 target = exception;
284 }
285 throw new ServiceException(
286 "Failed to create service of type: " + type,
287 target);
288 } catch (InstantiationException exception) {
289 throw new ServiceException(
290 "Failed to create service of type: " + type,
291 exception);
292 } finally {
293 creating.remove(type);
294 }
295 return service;
296 }
297
298 /***
299 * Populates all the public setters for the supplied service.
300 * <p/>
301 * The service must following bean naming conventions, and there must be a
302 * service registered for each of setter's arguments.
303 *
304 * @param service the service to populate.
305 * @throws ServiceException
306 */
307 private void invokeSetters(Object service) throws ServiceException {
308 PropertyDescriptor[] descriptors;
309 try {
310 BeanInfo info = Introspector.getBeanInfo(service.getClass());
311 descriptors = info.getPropertyDescriptors();
312 } catch (IntrospectionException exception) {
313 throw new ServiceException(exception.getMessage(), exception);
314 }
315 for (int i = 0; i < descriptors.length; ++i) {
316 PropertyDescriptor descriptor = descriptors[i];
317 Method method = descriptor.getWriteMethod();
318 if (method != null) {
319 Class type = descriptor.getPropertyType();
320 Object[] args = new Object[1];
321 args[0] = getService(type);
322 try {
323 method.invoke(service, args);
324 } catch (IllegalAccessException exception) {
325 throw new ServiceException(
326 "Failed to create service of type: " + type,
327 exception);
328 } catch (InvocationTargetException exception) {
329 Throwable target = exception.getTargetException();
330 if (target == null) {
331 target = exception;
332 }
333 throw new ServiceException(
334 "Failed to create service of type: " + type,
335 target);
336 }
337 }
338 }
339 }
340
341 /***
342 * Checks if a service has been registered.
343 *
344 * @param type the type of the service
345 * @throws ServiceAlreadyExistsException if the service is already
346 * registered
347 */
348 protected void checkExists(Class type)
349 throws ServiceAlreadyExistsException {
350 if (_services.get(type) != null) {
351 throw new ServiceAlreadyExistsException(
352 "Service of type " + type + " already registered");
353 }
354 }
355 }