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