View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.core.service;
21  
22  import java.util.AbstractSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.concurrent.Executor;
28  import java.util.concurrent.ExecutorService;
29  import java.util.concurrent.Executors;
30  import java.util.concurrent.TimeUnit;
31  import java.util.concurrent.atomic.AtomicInteger;
32  
33  import org.apache.mina.core.IoUtil;
34  import org.apache.mina.core.filterchain.DefaultIoFilterChain;
35  import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
36  import org.apache.mina.core.filterchain.IoFilterChainBuilder;
37  import org.apache.mina.core.future.ConnectFuture;
38  import org.apache.mina.core.future.DefaultIoFuture;
39  import org.apache.mina.core.future.IoFuture;
40  import org.apache.mina.core.future.WriteFuture;
41  import org.apache.mina.core.session.AbstractIoSession;
42  import org.apache.mina.core.session.DefaultIoSessionDataStructureFactory;
43  import org.apache.mina.core.session.IdleStatus;
44  import org.apache.mina.core.session.IoSession;
45  import org.apache.mina.core.session.IoSessionConfig;
46  import org.apache.mina.core.session.IoSessionDataStructureFactory;
47  import org.apache.mina.core.session.IoSessionInitializationException;
48  import org.apache.mina.core.session.IoSessionInitializer;
49  import org.apache.mina.util.ExceptionMonitor;
50  import org.apache.mina.util.NamePreservingRunnable;
51  
52  /**
53   * Base implementation of {@link IoService}s.
54   * 
55   * An instance of IoService contains an Executor which will handle the incoming
56   * events.
57   *
58   * @author The Apache MINA Project (dev@mina.apache.org)
59   * @version $Rev: 713957 $, $Date: 2008-11-14 10:27:16 +0100 (Fri, 14 Nov 2008) $
60   */
61  public abstract class AbstractIoService implements IoService {
62      /** 
63       * The unique number identifying the Service. It's incremented
64       * for each new IoService created.
65       */
66      private static final AtomicInteger id = new AtomicInteger();
67  
68      /** 
69       * The thread name built from the IoService inherited 
70       * instance class name and the IoService Id 
71       **/
72      private final String threadName;
73  
74      /**
75       * The associated executor, responsible for handling execution of I/O events.
76       */
77      private final Executor executor;
78  
79      /**
80       * A flag used to indicate that the local executor has been created
81       * inside this instance, and not passed by a caller.
82       * 
83       * If the executor is locally created, then it will be an instance
84       * of the ThreadPoolExecutor class.
85       */
86      private final boolean createdExecutor;
87  
88      /**
89       * The IoHandler in charge of managing all the I/O Events. It is 
90       */
91      private IoHandler handler;
92  
93      /**
94       * The default {@link IoSessionConfig} which will be used to configure new sessions.
95       */
96      private final IoSessionConfig sessionConfig;
97  
98      private final IoServiceListener serviceActivationListener = new IoServiceListener() {
99          public void serviceActivated(IoService service) {
100             // Update lastIoTime.
101             AbstractIoService s = (AbstractIoService) service;
102             IoServiceStatistics _stats = (IoServiceStatistics) s.getStatistics();
103             _stats.setLastReadTime(s.getActivationTime());
104             _stats.setLastWriteTime(s.getActivationTime());
105             _stats.setLastThroughputCalculationTime(s.getActivationTime());
106 
107         }
108 
109         public void serviceDeactivated(IoService service) {
110         }
111 
112         public void serviceIdle(IoService service, IdleStatus idleStatus) {
113         }
114 
115         public void sessionCreated(IoSession session) {
116         }
117 
118         public void sessionDestroyed(IoSession session) {
119         }
120     };
121 
122     /**
123      * Current filter chain builder.
124      */
125     private IoFilterChainBuilder filterChainBuilder = new DefaultIoFilterChainBuilder();
126 
127     private IoSessionDataStructureFactory sessionDataStructureFactory = new DefaultIoSessionDataStructureFactory();
128 
129     /**
130      * Maintains the {@link IoServiceListener}s of this service.
131      */
132     private final IoServiceListenerSupport listeners;
133 
134     /**
135      * A lock object which must be acquired when related resources are
136      * destroyed.
137      */
138     protected final Object disposalLock = new Object();
139 
140     private volatile boolean disposing;
141 
142     private volatile boolean disposed;
143 
144     private IoFuture disposalFuture;
145 
146     /**
147      * {@inheritDoc}
148      */
149     private IoServiceStatistics stats = new IoServiceStatistics(this);
150     
151 
152     /**
153      * Constructor for {@link AbstractIoService}. You need to provide a default
154      * session configuration and an {@link Executor} for handling I/O events. If
155      * a null {@link Executor} is provided, a default one will be created using
156      * {@link Executors#newCachedThreadPool()}.
157      * 
158      * @param sessionConfig
159      *            the default configuration for the managed {@link IoSession}
160      * @param executor
161      *            the {@link Executor} used for handling execution of I/O
162      *            events. Can be <code>null</code>.
163      */
164     protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) {
165         if (sessionConfig == null) {
166             throw new NullPointerException("sessionConfig");
167         }
168 
169         if (getTransportMetadata() == null) {
170             throw new NullPointerException("TransportMetadata");
171         }
172 
173         if (!getTransportMetadata().getSessionConfigType().isAssignableFrom(
174                 sessionConfig.getClass())) {
175             throw new IllegalArgumentException("sessionConfig type: "
176                     + sessionConfig.getClass() + " (expected: "
177                     + getTransportMetadata().getSessionConfigType() + ")");
178         }
179 
180         // Create the listeners, and add a first listener : a activation listener
181         // for this service, which will give information on the service state.
182         listeners = new IoServiceListenerSupport(this);
183         listeners.add(serviceActivationListener);
184 
185         // Stores the given session configuration
186         this.sessionConfig = sessionConfig;
187 
188         // Make JVM load the exception monitor before some transports
189         // change the thread context class loader.
190         ExceptionMonitor.getInstance();
191 
192         if (executor == null) {
193             this.executor = Executors.newCachedThreadPool();
194             createdExecutor = true;
195         } else {
196             this.executor = executor;
197             createdExecutor = false;
198         }
199 
200         threadName = getClass().getSimpleName() + '-' + id.incrementAndGet();
201     }
202 
203     /**
204      * {@inheritDoc}
205      */
206     public final IoFilterChainBuilder getFilterChainBuilder() {
207         return filterChainBuilder;
208     }
209 
210     /**
211      * {@inheritDoc}
212      */
213     public final void setFilterChainBuilder(IoFilterChainBuilder builder) {
214         if (builder == null) {
215             builder = new DefaultIoFilterChainBuilder();
216         }
217         filterChainBuilder = builder;
218     }
219 
220     /**
221      * {@inheritDoc}
222      */
223     public final DefaultIoFilterChainBuilder getFilterChain() {
224         if (filterChainBuilder instanceof DefaultIoFilterChainBuilder) {
225             return (DefaultIoFilterChainBuilder) filterChainBuilder;
226         } else {
227             throw new IllegalStateException(
228                     "Current filter chain builder is not a DefaultIoFilterChainBuilder.");
229         }
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     public final void addListener(IoServiceListener listener) {
236         listeners.add(listener);
237     }
238 
239     /**
240      * {@inheritDoc}
241      */
242     public final void removeListener(IoServiceListener listener) {
243         listeners.remove(listener);
244     }
245 
246     /**
247      * {@inheritDoc}
248      */
249     public final boolean isActive() {
250         return listeners.isActive();
251     }
252 
253     /**
254      * {@inheritDoc}
255      */
256     public final boolean isDisposing() {
257         return disposing;
258     }
259 
260     /**
261      * {@inheritDoc}
262      */
263     public final boolean isDisposed() {
264         return disposed;
265     }
266 
267     /**
268      * {@inheritDoc}
269      */
270     public final void dispose() {
271         if (disposed) {
272             return;
273         }
274 
275         IoFuture disposalFuture;
276         synchronized (disposalLock) {
277             disposalFuture = this.disposalFuture;
278             if (!disposing) {
279                 disposing = true;
280                 try {
281                     this.disposalFuture = disposalFuture = dispose0();
282                 } catch (Exception e) {
283                     ExceptionMonitor.getInstance().exceptionCaught(e);
284                 } finally {
285                     if (disposalFuture == null) {
286                         disposed = true;
287                     }
288                 }
289             }
290         }
291         
292         if (disposalFuture != null) {
293             disposalFuture.awaitUninterruptibly();
294         }
295 
296         if (createdExecutor) {
297             ExecutorService e = (ExecutorService) executor;
298             e.shutdown();
299             while (!e.isTerminated()) {
300                 try {
301                     e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
302                 } catch (InterruptedException e1) {
303                     // Ignore; it should end shortly.
304                 }
305             }
306         }
307 
308         disposed = true;
309     }
310 
311     /**
312      * Implement this method to release any acquired resources.  This method
313      * is invoked only once by {@link #dispose()}.
314      */
315     protected abstract IoFuture dispose0() throws Exception;
316 
317     /**
318      * {@inheritDoc}
319      */
320     public final Map<Long, IoSession> getManagedSessions() {
321         return listeners.getManagedSessions();
322     }
323 
324     /**
325      * {@inheritDoc}
326      */
327     public final int getManagedSessionCount() {
328         return listeners.getManagedSessionCount();
329     }
330 
331     /**
332      * {@inheritDoc}
333      */
334     public final IoHandler getHandler() {
335         return handler;
336     }
337 
338     /**
339      * {@inheritDoc}
340      */
341     public final void setHandler(IoHandler handler) {
342         if (handler == null) {
343             throw new NullPointerException("handler cannot be null");
344         }
345 
346         if (isActive()) {
347             throw new IllegalStateException(
348                     "handler cannot be set while the service is active.");
349         }
350 
351         this.handler = handler;
352     }
353 
354     /**
355      * {@inheritDoc}
356      */
357     public IoSessionConfig getSessionConfig() {
358         return sessionConfig;
359     }
360 
361     /**
362      * {@inheritDoc}
363      */
364     public final IoSessionDataStructureFactory getSessionDataStructureFactory() {
365         return sessionDataStructureFactory;
366     }
367 
368     /**
369      * {@inheritDoc}
370      */
371     public final void setSessionDataStructureFactory(
372             IoSessionDataStructureFactory sessionDataStructureFactory) {
373         if (sessionDataStructureFactory == null) {
374             throw new NullPointerException("sessionDataStructureFactory");
375         }
376 
377         if (isActive()) {
378             throw new IllegalStateException(
379                     "sessionDataStructureFactory cannot be set while the service is active.");
380         }
381 
382         this.sessionDataStructureFactory = sessionDataStructureFactory;
383     }
384 
385     /**
386      * {@inheritDoc}
387      */
388     public IoServiceStatistics getStatistics() {
389         return stats;
390     }
391 
392     /**
393      * {@inheritDoc}
394      */
395     public final long getActivationTime() {
396         return listeners.getActivationTime();
397     }
398 
399     /**
400      * {@inheritDoc}
401      */
402     public final Set<WriteFuture> broadcast(Object message) {
403         // Convert to Set.  We do not return a List here because only the
404         // direct caller of MessageBroadcaster knows the order of write
405         // operations.
406         final List<WriteFuture> futures = IoUtil.broadcast(message,
407                 getManagedSessions().values());
408         return new AbstractSet<WriteFuture>() {
409             @Override
410             public Iterator<WriteFuture> iterator() {
411                 return futures.iterator();
412             }
413 
414             @Override
415             public int size() {
416                 return futures.size();
417             }
418         };
419     }
420 
421     public final IoServiceListenerSupport getListeners() {
422         return listeners;
423     }
424 
425 
426     protected final void executeWorker(Runnable worker) {
427         executeWorker(worker, null);
428     }
429 
430     protected final void executeWorker(Runnable worker, String suffix) {
431         String actualThreadName = threadName;
432         if (suffix != null) {
433             actualThreadName = actualThreadName + '-' + suffix;
434         }
435         executor.execute(new NamePreservingRunnable(worker, actualThreadName));
436     }
437 
438     // TODO Figure out make it work without causing a compiler error / warning.
439     @SuppressWarnings("unchecked")
440     protected final void finishSessionInitialization(IoSession session,
441             IoFuture future, IoSessionInitializer sessionInitializer) {
442         // Update lastIoTime if needed.
443         if (stats.getLastReadTime() == 0) {
444             ((IoServiceStatistics)stats).setLastReadTime(getActivationTime());
445         }
446         if (stats.getLastWriteTime() == 0) {
447             ((IoServiceStatistics)stats).setLastWriteTime(getActivationTime());
448         }
449 
450         // Every property but attributeMap should be set now.
451         // Now initialize the attributeMap.  The reason why we initialize
452         // the attributeMap at last is to make sure all session properties
453         // such as remoteAddress are provided to IoSessionDataStructureFactory.
454         try {
455             ((AbstractIoSession) session).setAttributeMap(session.getService()
456                     .getSessionDataStructureFactory().getAttributeMap(session));
457         } catch (IoSessionInitializationException e) {
458             throw e;
459         } catch (Exception e) {
460             throw new IoSessionInitializationException(
461                     "Failed to initialize an attributeMap.", e);
462         }
463 
464         try {
465             ((AbstractIoSession) session).setWriteRequestQueue(session
466                     .getService().getSessionDataStructureFactory()
467                     .getWriteRequestQueue(session));
468         } catch (IoSessionInitializationException e) {
469             throw e;
470         } catch (Exception e) {
471             throw new IoSessionInitializationException(
472                     "Failed to initialize a writeRequestQueue.", e);
473         }
474 
475         if (future != null && future instanceof ConnectFuture) {
476             // DefaultIoFilterChain will notify the future. (We support ConnectFuture only for now).
477             session.setAttribute(DefaultIoFilterChain.SESSION_CREATED_FUTURE,
478                     future);
479         }
480 
481         if (sessionInitializer != null) {
482             sessionInitializer.initializeSession(session, future);
483         }
484 
485         finishSessionInitialization0(session, future);
486     }
487 
488     /**
489      * Implement this method to perform additional tasks required for session
490      * initialization. Do not call this method directly;
491      * {@link #finishSessionInitialization(IoSession, IoFuture, IoSessionInitializer)} will call
492      * this method instead.
493      */
494     protected void finishSessionInitialization0(IoSession session,
495             IoFuture future) {
496     }
497 
498     protected static class ServiceOperationFuture extends DefaultIoFuture {
499         public ServiceOperationFuture() {
500             super(null);
501         }
502 
503         public final boolean isDone() {
504             return getValue() == Boolean.TRUE;
505         }
506 
507         public final void setDone() {
508             setValue(Boolean.TRUE);
509         }
510 
511         public final Exception getException() {
512             if (getValue() instanceof Exception) {
513                 return (Exception) getValue();
514             } else {
515                 return null;
516             }
517         }
518 
519         public final void setException(Exception exception) {
520             if (exception == null) {
521                 throw new NullPointerException("exception");
522             }
523             setValue(exception);
524         }
525     }
526 
527     /**
528      * {@inheritDoc}
529      */
530     public int getScheduledWriteBytes() {
531         return stats.getScheduledWriteBytes();
532     }
533 
534     /**
535      * {@inheritDoc}
536      */
537     public int getScheduledWriteMessages() {
538         return stats.getScheduledWriteMessages();
539     }
540     
541 }