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.filter.codec.demux;
21  
22  import java.util.Map;
23  import java.util.Set;
24  import java.util.concurrent.ConcurrentHashMap;
25  
26  import org.apache.mina.core.session.AttributeKey;
27  import org.apache.mina.core.session.IoSession;
28  import org.apache.mina.core.session.UnknownMessageTypeException;
29  import org.apache.mina.filter.codec.ProtocolEncoder;
30  import org.apache.mina.filter.codec.ProtocolEncoderOutput;
31  import org.apache.mina.util.CopyOnWriteMap;
32  import org.apache.mina.util.IdentityHashSet;
33  
34  /**
35   * A composite {@link ProtocolEncoder} that demultiplexes incoming message
36   * encoding requests into an appropriate {@link MessageEncoder}.
37   *
38   * <h2>Disposing resources acquired by {@link MessageEncoder}</h2>
39   * <p>
40   * Override {@link #dispose(IoSession)} method. Please don't forget to call
41   * <tt>super.dispose()</tt>.
42   *
43   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
44   *
45   * @see MessageEncoderFactory
46   * @see MessageEncoder
47   */
48  public class DemuxingProtocolEncoder implements ProtocolEncoder {
49      
50      private final AttributeKey STATE = new AttributeKey(getClass(), "state");
51  
52      @SuppressWarnings("rawtypes")
53      private final Map<Class<?>, MessageEncoderFactory> type2encoderFactory = new CopyOnWriteMap<Class<?>, MessageEncoderFactory>();
54  
55      private static final Class<?>[] EMPTY_PARAMS = new Class[0];
56  
57      public DemuxingProtocolEncoder() {
58          // Do nothing
59      }
60  
61      @SuppressWarnings({ "rawtypes", "unchecked" })
62      public void addMessageEncoder(Class<?> messageType, Class<? extends MessageEncoder> encoderClass) {
63          if (encoderClass == null) {
64              throw new IllegalArgumentException("encoderClass");
65          }
66  
67          try {
68              encoderClass.getConstructor(EMPTY_PARAMS);
69          } catch (NoSuchMethodException e) {
70              throw new IllegalArgumentException(
71                      "The specified class doesn't have a public default constructor.");
72          }
73  
74          boolean registered = false;
75          if (MessageEncoder.class.isAssignableFrom(encoderClass)) {
76              addMessageEncoder(messageType, new DefaultConstructorMessageEncoderFactory(encoderClass));
77              registered = true;
78          }
79  
80          if (!registered) {
81              throw new IllegalArgumentException(
82                      "Unregisterable type: " + encoderClass);
83          }
84      }
85  
86      @SuppressWarnings({ "unchecked", "rawtypes" })
87      public <T> void addMessageEncoder(Class<T> messageType, MessageEncoder<? super T> encoder) {
88          addMessageEncoder(messageType, new SingletonMessageEncoderFactory(encoder));
89      }
90  
91      public <T> void addMessageEncoder(Class<T> messageType, MessageEncoderFactory<? super T> factory) {
92          if (messageType == null) {
93              throw new IllegalArgumentException("messageType");
94          }
95          
96          if (factory == null) {
97              throw new IllegalArgumentException("factory");
98          }
99          
100         synchronized (type2encoderFactory) {
101             if (type2encoderFactory.containsKey(messageType)) {
102                 throw new IllegalStateException(
103                         "The specified message type (" + messageType.getName() + ") is registered already.");
104             }
105             
106             type2encoderFactory.put(messageType, factory);
107         }
108     }
109 
110     @SuppressWarnings("rawtypes")
111     public void addMessageEncoder(Iterable<Class<?>> messageTypes, Class<? extends MessageEncoder> encoderClass) {
112         for (Class<?> messageType : messageTypes) {
113             addMessageEncoder(messageType, encoderClass);
114         }
115     }
116     
117     public <T> void addMessageEncoder(Iterable<Class<? extends T>> messageTypes, MessageEncoder<? super T> encoder) {
118         for (Class<? extends T> messageType : messageTypes) {
119             addMessageEncoder(messageType, encoder);
120         }
121     }
122     
123     public <T> void addMessageEncoder(Iterable<Class<? extends T>> messageTypes, MessageEncoderFactory<? super T> factory) {
124         for (Class<? extends T> messageType : messageTypes) {
125             addMessageEncoder(messageType, factory);
126         }
127     }
128     
129     /**
130      * {@inheritDoc}
131      */
132     public void encode(IoSession session, Object message,
133             ProtocolEncoderOutput out) throws Exception {
134         State state = getState(session);
135         MessageEncoder<Object> encoder = findEncoder(state, message.getClass());
136         if (encoder != null) {
137             encoder.encode(session, message, out);
138         } else {
139             throw new UnknownMessageTypeException(
140                     "No message encoder found for message: " + message);
141         }
142     }
143 
144     protected MessageEncoder<Object> findEncoder(State state, Class<?> type) {
145         return findEncoder(state, type, null);
146     }
147 
148     @SuppressWarnings("unchecked")
149     private MessageEncoder<Object> findEncoder(
150             State state, Class<?> type, Set<Class<?>> triedClasses) {
151         @SuppressWarnings("rawtypes")
152         MessageEncoder encoder = null;
153 
154         if (triedClasses != null && triedClasses.contains(type)) {
155             return null;
156         }
157 
158         /*
159          * Try the cache first.
160          */
161         encoder = state.findEncoderCache.get(type);
162         
163         if (encoder != null) {
164             return encoder;
165         }
166 
167         /*
168          * Try the registered encoders for an immediate match.
169          */
170         encoder = state.type2encoder.get(type);
171 
172         if (encoder == null) {
173             /*
174              * No immediate match could be found. Search the type's interfaces.
175              */
176 
177             if (triedClasses == null) {
178                 triedClasses = new IdentityHashSet<Class<?>>();
179             }
180             
181             triedClasses.add(type);
182 
183             Class<?>[] interfaces = type.getInterfaces();
184             
185             for (Class<?> element : interfaces) {
186                 encoder = findEncoder(state, element, triedClasses);
187                 
188                 if (encoder != null) {
189                     break;
190                 }
191             }
192         }
193 
194         if (encoder == null) {
195             /*
196              * No match in type's interfaces could be found. Search the
197              * superclass.
198              */
199 
200             Class<?> superclass = type.getSuperclass();
201             
202             if (superclass != null) {
203                 encoder = findEncoder(state, superclass);
204             }
205         }
206 
207         /*
208          * Make sure the encoder is added to the cache. By updating the cache
209          * here all the types (superclasses and interfaces) in the path which
210          * led to a match will be cached along with the immediate message type.
211          */
212         if (encoder != null) {
213             state.findEncoderCache.put(type, encoder);
214             MessageEncoder<Object> tmpEncoder = state.findEncoderCache.putIfAbsent(type, encoder);
215             
216             if (tmpEncoder != null) {
217                 encoder = tmpEncoder;
218             }
219         }
220 
221         return encoder;
222     }
223 
224     /**
225      * {@inheritDoc}
226      */
227     public void dispose(IoSession session) throws Exception {
228         session.removeAttribute(STATE);
229     }
230     
231     private State getState(IoSession session) throws Exception {
232         State state = (State) session.getAttribute(STATE);
233         if (state == null) {
234             state = new State();
235             State oldState = (State) session.setAttributeIfAbsent(STATE, state);
236             if (oldState != null) {
237                 state = oldState;
238             }
239         }
240         return state;
241     }
242     
243     private class State {
244         @SuppressWarnings("rawtypes")
245         private final ConcurrentHashMap<Class<?>, MessageEncoder> findEncoderCache = new ConcurrentHashMap<Class<?>, MessageEncoder>();
246 
247         @SuppressWarnings("rawtypes")
248         private final Map<Class<?>, MessageEncoder> type2encoder = new ConcurrentHashMap<Class<?>, MessageEncoder>();
249         
250         @SuppressWarnings("rawtypes")
251         private State() throws Exception {
252             for (Map.Entry<Class<?>, MessageEncoderFactory> e: type2encoderFactory.entrySet()) {
253                 type2encoder.put(e.getKey(), e.getValue().getEncoder());
254             }
255         }
256     }
257 
258     private static class SingletonMessageEncoderFactory<T> implements
259             MessageEncoderFactory<T> {
260         private final MessageEncoder<T> encoder;
261 
262         private SingletonMessageEncoderFactory(MessageEncoder<T> encoder) {
263             if (encoder == null) {
264                 throw new IllegalArgumentException("encoder");
265             }
266             this.encoder = encoder;
267         }
268 
269         public MessageEncoder<T> getEncoder() {
270             return encoder;
271         }
272     }
273 
274     private static class DefaultConstructorMessageEncoderFactory<T> implements
275             MessageEncoderFactory<T> {
276         private final Class<MessageEncoder<T>> encoderClass;
277 
278         private DefaultConstructorMessageEncoderFactory(Class<MessageEncoder<T>> encoderClass) {
279             if (encoderClass == null) {
280                 throw new IllegalArgumentException("encoderClass");
281             }
282 
283             if (!MessageEncoder.class.isAssignableFrom(encoderClass)) {
284                 throw new IllegalArgumentException(
285                         "encoderClass is not assignable to MessageEncoder");
286             }
287             this.encoderClass = encoderClass;
288         }
289 
290         public MessageEncoder<T> getEncoder() throws Exception {
291             return encoderClass.newInstance();
292         }
293     }
294 }