001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.filter.codec.demux;
021:
022: import java.util.HashMap;
023: import java.util.Map;
024: import java.util.Set;
025:
026: import org.apache.mina.common.AttributeKey;
027: import org.apache.mina.common.IoSession;
028: import org.apache.mina.common.UnknownMessageTypeException;
029: import org.apache.mina.filter.codec.ProtocolEncoder;
030: import org.apache.mina.filter.codec.ProtocolEncoderOutput;
031: import org.apache.mina.util.CopyOnWriteMap;
032: import org.apache.mina.util.IdentityHashSet;
033:
034: /**
035: * A composite {@link ProtocolEncoder} that demultiplexes incoming message
036: * encoding requests into an appropriate {@link MessageEncoder}.
037: *
038: * <h2>Disposing resources acquired by {@link MessageEncoder}</h2>
039: * <p>
040: * Override {@link #dispose(IoSession)} method. Please don't forget to call
041: * <tt>super.dispose()</tt>.
042: *
043: * @author The Apache MINA Project (dev@mina.apache.org)
044: * @version $Rev: 593385 $, $Date: 2007-11-08 19:02:40 -0700 (Thu, 08 Nov 2007) $
045: *
046: * @see MessageEncoderFactory
047: * @see MessageEncoder
048: */
049: public class DemuxingProtocolEncoder implements ProtocolEncoder {
050:
051: private final AttributeKey STATE = new AttributeKey(getClass(),
052: "state");
053:
054: @SuppressWarnings("unchecked")
055: private final Map<Class<?>, MessageEncoderFactory> type2encoderFactory = new CopyOnWriteMap<Class<?>, MessageEncoderFactory>();
056:
057: private static final Class<?>[] EMPTY_PARAMS = new Class[0];
058:
059: public DemuxingProtocolEncoder() {
060: }
061:
062: @SuppressWarnings("unchecked")
063: public void addMessageEncoder(Class<?> messageType,
064: Class<? extends MessageEncoder> encoderClass) {
065: if (encoderClass == null) {
066: throw new NullPointerException("encoderClass");
067: }
068:
069: try {
070: encoderClass.getConstructor(EMPTY_PARAMS);
071: } catch (NoSuchMethodException e) {
072: throw new IllegalArgumentException(
073: "The specified class doesn't have a public default constructor.");
074: }
075:
076: boolean registered = false;
077: if (MessageEncoder.class.isAssignableFrom(encoderClass)) {
078: addMessageEncoder(messageType,
079: new DefaultConstructorMessageEncoderFactory(
080: encoderClass));
081: registered = true;
082: }
083:
084: if (!registered) {
085: throw new IllegalArgumentException("Unregisterable type: "
086: + encoderClass);
087: }
088: }
089:
090: @SuppressWarnings("unchecked")
091: public <T> void addMessageEncoder(Class<T> messageType,
092: MessageEncoder<? super T> encoder) {
093: addMessageEncoder(messageType,
094: new SingletonMessageEncoderFactory(encoder));
095: }
096:
097: public <T> void addMessageEncoder(Class<T> messageType,
098: MessageEncoderFactory<? super T> factory) {
099: if (messageType == null) {
100: throw new NullPointerException("messageType");
101: }
102:
103: if (factory == null) {
104: throw new NullPointerException("factory");
105: }
106:
107: synchronized (type2encoderFactory) {
108: if (type2encoderFactory.containsKey(messageType)) {
109: throw new IllegalStateException(
110: "The specified message type ("
111: + messageType.getName()
112: + ") is registered already.");
113: }
114:
115: type2encoderFactory.put(messageType, factory);
116: }
117: }
118:
119: @SuppressWarnings("unchecked")
120: public void addMessageEncoder(Iterable<Class<?>> messageTypes,
121: Class<? extends MessageEncoder> encoderClass) {
122: for (Class<?> messageType : messageTypes) {
123: addMessageEncoder(messageType, encoderClass);
124: }
125: }
126:
127: public <T> void addMessageEncoder(
128: Iterable<Class<? extends T>> messageTypes,
129: MessageEncoder<? super T> encoder) {
130: for (Class<? extends T> messageType : messageTypes) {
131: addMessageEncoder(messageType, encoder);
132: }
133: }
134:
135: public <T> void addMessageEncoder(
136: Iterable<Class<? extends T>> messageTypes,
137: MessageEncoderFactory<? super T> factory) {
138: for (Class<? extends T> messageType : messageTypes) {
139: addMessageEncoder(messageType, factory);
140: }
141: }
142:
143: public void encode(IoSession session, Object message,
144: ProtocolEncoderOutput out) throws Exception {
145: State state = getState(session);
146: MessageEncoder<Object> encoder = findEncoder(state, message
147: .getClass());
148: if (encoder != null) {
149: encoder.encode(session, message, out);
150: } else {
151: throw new UnknownMessageTypeException(
152: "No message encoder found for message: " + message);
153: }
154: }
155:
156: protected MessageEncoder<Object> findEncoder(State state,
157: Class<?> type) {
158: return findEncoder(state, type, null);
159: }
160:
161: @SuppressWarnings("unchecked")
162: private MessageEncoder<Object> findEncoder(State state, Class type,
163: Set<Class> triedClasses) {
164: MessageEncoder encoder = null;
165:
166: if (triedClasses != null && triedClasses.contains(type)) {
167: return null;
168: }
169:
170: /*
171: * Try the cache first.
172: */
173: encoder = state.findEncoderCache.get(type);
174: if (encoder != null) {
175: return encoder;
176: }
177:
178: /*
179: * Try the registered encoders for an immediate match.
180: */
181: encoder = state.type2encoder.get(type);
182:
183: if (encoder == null) {
184: /*
185: * No immediate match could be found. Search the type's interfaces.
186: */
187:
188: if (triedClasses == null) {
189: triedClasses = new IdentityHashSet<Class>();
190: }
191: triedClasses.add(type);
192:
193: Class[] interfaces = type.getInterfaces();
194: for (Class element : interfaces) {
195: encoder = findEncoder(state, element, triedClasses);
196: if (encoder != null) {
197: break;
198: }
199: }
200: }
201:
202: if (encoder == null) {
203: /*
204: * No match in type's interfaces could be found. Search the
205: * superclass.
206: */
207:
208: Class super class = type.getSuperclass();
209: if (super class != null) {
210: encoder = findEncoder(state, super class);
211: }
212: }
213:
214: /*
215: * Make sure the encoder is added to the cache. By updating the cache
216: * here all the types (superclasses and interfaces) in the path which
217: * led to a match will be cached along with the immediate message type.
218: */
219: if (encoder != null) {
220: state.findEncoderCache.put(type, encoder);
221: }
222:
223: return encoder;
224: }
225:
226: public void dispose(IoSession session) throws Exception {
227: session.removeAttribute(STATE);
228: }
229:
230: private State getState(IoSession session) throws Exception {
231: State state = (State) session.getAttribute(STATE);
232: if (state == null) {
233: state = new State();
234: State oldState = (State) session.setAttributeIfAbsent(
235: STATE, state);
236: if (oldState != null) {
237: state = oldState;
238: }
239: }
240: return state;
241: }
242:
243: private class State {
244: @SuppressWarnings("unchecked")
245: private final Map<Class<?>, MessageEncoder> findEncoderCache = new HashMap<Class<?>, MessageEncoder>();
246:
247: @SuppressWarnings("unchecked")
248: private final Map<Class<?>, MessageEncoder> type2encoder = new HashMap<Class<?>, MessageEncoder>();
249:
250: @SuppressWarnings("unchecked")
251: private State() throws Exception {
252: for (Map.Entry<Class<?>, MessageEncoderFactory> e : type2encoderFactory
253: .entrySet()) {
254: type2encoder.put(e.getKey(), e.getValue().getEncoder());
255: }
256: }
257: }
258:
259: private static class SingletonMessageEncoderFactory<T> implements
260: MessageEncoderFactory<T> {
261: private final MessageEncoder<T> encoder;
262:
263: private SingletonMessageEncoderFactory(MessageEncoder<T> encoder) {
264: if (encoder == null) {
265: throw new NullPointerException("encoder");
266: }
267: this .encoder = encoder;
268: }
269:
270: public MessageEncoder<T> getEncoder() {
271: return encoder;
272: }
273: }
274:
275: private static class DefaultConstructorMessageEncoderFactory<T>
276: implements MessageEncoderFactory<T> {
277: private final Class<MessageEncoder<T>> encoderClass;
278:
279: private DefaultConstructorMessageEncoderFactory(
280: Class<MessageEncoder<T>> encoderClass) {
281: if (encoderClass == null) {
282: throw new NullPointerException("encoderClass");
283: }
284:
285: if (!MessageEncoder.class.isAssignableFrom(encoderClass)) {
286: throw new IllegalArgumentException(
287: "encoderClass is not assignable to MessageEncoder");
288: }
289: this .encoderClass = encoderClass;
290: }
291:
292: public MessageEncoder<T> getEncoder() throws Exception {
293: return encoderClass.newInstance();
294: }
295: }
296: }
|