View Javadoc

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 }