/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.mina.filter.codec.demux;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderException;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderException;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.util.IdentityHashSet;
/**
* A composite {@link ProtocolCodecFactory} that consists of multiple
* {@link MessageEncoder}s and {@link MessageDecoder}s.
* {@link ProtocolEncoder} and {@link ProtocolDecoder} this factory
* returns demultiplex incoming messages and buffers to
* appropriate {@link MessageEncoder}s and {@link MessageDecoder}s.
*
* <h2>Disposing resources acquired by {@link MessageEncoder} and {@link MessageDecoder}</h2>
* <p>
* Make your {@link MessageEncoder} and {@link MessageDecoder} to put all
* resources that need to be released as a session attribute. {@link #disposeCodecResources(IoSession)}
* method will be invoked when a session is closed. Override {@link #disposeCodecResources(IoSession)}
* to release the resources you've put as an attribute.
* <p>
* We didn't provide any <tt>dispose</tt> method for {@link MessageEncoder} and {@link MessageDecoder}
* because they can give you a big performance penalty in case you have a lot of
* message types to handle.
*
* @author The Apache Directory Project (mina-dev@directory.apache.org)
* @version $Rev: 525038 $, $Date: 2007-04-03 14:25:09 +0900 (화, 03 4월 2007) $
*
* @see MessageEncoder
* @see MessageDecoder
*/
public class DemuxingProtocolCodecFactory implements ProtocolCodecFactory
{
private MessageDecoderFactory[] decoderFactories = new MessageDecoderFactory[0];
private MessageEncoderFactory[] encoderFactories = new MessageEncoderFactory[0];
private static final Class[] EMPTY_PARAMS = new Class[0];
public DemuxingProtocolCodecFactory()
{
}
public void register( Class<?> encoderOrDecoderClass )
{
if( encoderOrDecoderClass == null )
{
throw new NullPointerException( "encoderOrDecoderClass" );
}
try
{
encoderOrDecoderClass.getConstructor( EMPTY_PARAMS );
}
catch( NoSuchMethodException e )
{
throw new IllegalArgumentException( "The specifiec class doesn't have a public default constructor." );
}
boolean registered = false;
if( MessageEncoder.class.isAssignableFrom( encoderOrDecoderClass ) )
{
register( new DefaultConstructorMessageEncoderFactory( encoderOrDecoderClass ) );
registered = true;
}
if( MessageDecoder.class.isAssignableFrom( encoderOrDecoderClass ) )
{
register( new DefaultConstructorMessageDecoderFactory( encoderOrDecoderClass ) );
registered = true;
}
if( !registered )
{
throw new IllegalArgumentException( "Unregisterable type: " + encoderOrDecoderClass );
}
}
public void register( MessageEncoder encoder )
{
register( new SingletonMessageEncoderFactory( encoder ) );
}
public void register( MessageEncoderFactory factory )
{
if( factory == null )
{
throw new NullPointerException( "factory" );
}
MessageEncoderFactory[] encoderFactories = this.encoderFactories;
MessageEncoderFactory[] newEncoderFactories = new MessageEncoderFactory[ encoderFactories.length + 1 ];
System.arraycopy( encoderFactories, 0, newEncoderFactories, 0, encoderFactories.length );
newEncoderFactories[ encoderFactories.length ] = factory;
this.encoderFactories = newEncoderFactories;
}
public void register( MessageDecoder decoder )
{
register( new SingletonMessageDecoderFactory( decoder ) );
}
public void register( MessageDecoderFactory factory )
{
if( factory == null )
{
throw new NullPointerException( "factory" );
}
MessageDecoderFactory[] decoderFactories = this.decoderFactories;
MessageDecoderFactory[] newDecoderFactories = new MessageDecoderFactory[ decoderFactories.length + 1 ];
System.arraycopy( decoderFactories, 0, newDecoderFactories, 0, decoderFactories.length );
newDecoderFactories[ decoderFactories.length ] = factory;
this.decoderFactories = newDecoderFactories;
}
public ProtocolEncoder getEncoder() throws Exception
{
return new ProtocolEncoderImpl();
}
public ProtocolDecoder getDecoder() throws Exception
{
return new ProtocolDecoderImpl();
}
/**
* Implement this method to release all resources acquired to perform
* encoding and decoding messages for the specified <tt>session</tt>.
* By default, this method does nothing.
*
* @param session the session that requires resource deallocation now
*/
protected void disposeCodecResources( IoSession session )
{
// Do nothing by default; let users implement it as they want.
// This statement is just to avoid compiler warning. Please ignore.
session.getTransportType();
}
private class ProtocolEncoderImpl implements ProtocolEncoder
{
private final Map<Class, MessageEncoder> encoders = new IdentityHashMap<Class, MessageEncoder>();
private ProtocolEncoderImpl() throws Exception
{
MessageEncoderFactory[] encoderFactories = DemuxingProtocolCodecFactory.this.encoderFactories;
for( int i = encoderFactories.length - 1; i >= 0; i-- )
{
MessageEncoder encoder = encoderFactories[ i ].getEncoder();
Set<Class> messageTypes = encoder.getMessageTypes();
if (messageTypes == null) {
throw new IllegalStateException(
encoder.getClass().getName() + "#getMessageTypes() may not return null.");
}
Iterator it = messageTypes.iterator();
while( it.hasNext() )
{
Class type = ( Class ) it.next();
encoders.put( type, encoder );
}
}
}
public void encode( IoSession session, Object message,
ProtocolEncoderOutput out ) throws Exception
{
Class type = message.getClass();
MessageEncoder encoder = findEncoder( type );
if( encoder == null )
{
throw new ProtocolEncoderException( "Unexpected message type: " + type );
}
encoder.encode( session, message, out );
}
private MessageEncoder findEncoder( Class type )
{
MessageEncoder encoder = encoders.get( type );
if( encoder == null )
{
encoder = findEncoder( type, new IdentityHashSet<Class>() );
}
return encoder;
}
private MessageEncoder findEncoder( Class type, Set<Class> triedClasses )
{
MessageEncoder encoder;
if( triedClasses.contains( type ) )
return null;
triedClasses.add( type );
encoder = encoders.get( type );
if( encoder == null )
{
encoder = findEncoder( type, triedClasses );
if( encoder != null )
return encoder;
Class[] interfaces = type.getInterfaces();
for( int i = 0; i < interfaces.length; i ++ )
{
encoder = findEncoder( interfaces[ i ], triedClasses );
if( encoder != null )
return encoder;
}
return null;
}
else
return encoder;
}
public void dispose( IoSession session ) throws Exception
{
DemuxingProtocolCodecFactory.this.disposeCodecResources( session );
}
}
private class ProtocolDecoderImpl extends CumulativeProtocolDecoder
{
private final MessageDecoder[] decoders;
private MessageDecoder currentDecoder;
protected ProtocolDecoderImpl() throws Exception
{
MessageDecoderFactory[] decoderFactories = DemuxingProtocolCodecFactory.this.decoderFactories;
decoders = new MessageDecoder[ decoderFactories.length ];
for( int i = decoderFactories.length - 1; i >= 0; i-- )
{
decoders[ i ] = decoderFactories[ i ].getDecoder();
}
}
protected boolean doDecode( IoSession session, ByteBuffer in,
ProtocolDecoderOutput out ) throws Exception
{
if( currentDecoder == null )
{
MessageDecoder[] decoders = this.decoders;
int undecodables = 0;
for( int i = decoders.length - 1; i >= 0; i -- )
{
MessageDecoder decoder = decoders[i];
int limit = in.limit();
int pos = in.position();
MessageDecoderResult result;
try
{
result = decoder.decodable( session, in );
}
finally
{
in.position( pos );
in.limit( limit );
}
if( result == MessageDecoder.OK )
{
currentDecoder = decoder;
break;
}
else if( result == MessageDecoder.NOT_OK )
{
undecodables ++;
}
else if( result != MessageDecoder.NEED_DATA )
{
throw new IllegalStateException( "Unexpected decode result (see your decodable()): " + result );
}
}
if( undecodables == decoders.length )
{
// Throw an exception if all decoders cannot decode data.
String dump = in.getHexDump();
in.position( in.limit() ); // Skip data
throw new ProtocolDecoderException(
"No appropriate message decoder: " + dump );
}
if( currentDecoder == null )
{
// Decoder is not determined yet (i.e. we need more data)
return false;
}
}
MessageDecoderResult result = currentDecoder.decode( session, in, out );
if( result == MessageDecoder.OK )
{
currentDecoder = null;
return true;
}
else if( result == MessageDecoder.NEED_DATA )
{
return false;
}
else if( result == MessageDecoder.NOT_OK )
{
throw new ProtocolDecoderException( "Message decoder returned NOT_OK." );
}
else
{
throw new IllegalStateException( "Unexpected decode result (see your decode()): " + result );
}
}
public void finishDecode( IoSession session, ProtocolDecoderOutput out ) throws Exception
{
if( currentDecoder == null )
{
return;
}
currentDecoder.finishDecode( session, out );
}
public void dispose( IoSession session ) throws Exception
{
super.dispose( session );
// ProtocolEncoder.dispose() already called disposeCodec(),
// so there's nothing more we need to do.
}
}
private static class SingletonMessageEncoderFactory implements MessageEncoderFactory
{
private final MessageEncoder encoder;
private SingletonMessageEncoderFactory( MessageEncoder encoder )
{
if( encoder == null )
{
throw new NullPointerException( "encoder" );
}
this.encoder = encoder;
}
public MessageEncoder getEncoder()
{
return encoder;
}
}
private static class SingletonMessageDecoderFactory implements MessageDecoderFactory
{
private final MessageDecoder decoder;
private SingletonMessageDecoderFactory( MessageDecoder decoder )
{
if( decoder == null )
{
throw new NullPointerException( "decoder" );
}
this.decoder = decoder;
}
public MessageDecoder getDecoder()
{
return decoder;
}
}
private static class DefaultConstructorMessageEncoderFactory implements MessageEncoderFactory
{
private final Class encoderClass;
private DefaultConstructorMessageEncoderFactory( Class encoderClass )
{
if( encoderClass == null )
{
throw new NullPointerException( "encoderClass" );
}
if( !MessageEncoder.class.isAssignableFrom( encoderClass ) )
{
throw new IllegalArgumentException( "encoderClass is not assignable to MessageEncoder" );
}
this.encoderClass = encoderClass;
}
public MessageEncoder getEncoder() throws Exception
{
return ( MessageEncoder ) encoderClass.newInstance();
}
}
private static class DefaultConstructorMessageDecoderFactory implements MessageDecoderFactory
{
private final Class decoderClass;
private DefaultConstructorMessageDecoderFactory( Class decoderClass )
{
if( decoderClass == null )
{
throw new NullPointerException( "decoderClass" );
}
if( !MessageDecoder.class.isAssignableFrom( decoderClass ) )
{
throw new IllegalArgumentException( "decoderClass is not assignable to MessageDecoder" );
}
this.decoderClass = decoderClass;
}
public MessageDecoder getDecoder() throws Exception
{
return ( MessageDecoder ) decoderClass.newInstance();
}
}
}