package org.apache.jcs.auxiliary.lateral.socket.tcp.discovery;
/*
* 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.
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jcs.auxiliary.lateral.LateralCacheAttributes;
import org.apache.jcs.auxiliary.lateral.LateralCacheInfo;
import org.apache.jcs.auxiliary.lateral.LateralCacheNoWait;
import org.apache.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheManager;
import org.apache.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes;
import org.apache.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
import org.apache.jcs.engine.behavior.ICache;
import org.apache.jcs.engine.behavior.ICompositeCacheManager;
import org.apache.jcs.engine.behavior.IShutdownObserver;
import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
/**
* Receives UDP Discovery messages.
*/
public class UDPDiscoveryReceiver
implements Runnable, IShutdownObserver
{
private final static Log log = LogFactory.getLog( UDPDiscoveryReceiver.class );
private final byte[] m_buffer = new byte[65536];
private MulticastSocket m_socket;
// todo consider using the threadpool manager to
// get this thread pool
// for now place a tight restrcition on the pool size
private static final int maxPoolSize = 10;
private PooledExecutor pooledExecutor = null;
// number of messages received.
private int cnt = 0;
/**
* Service to get cache names and hande request broadcasts
*/
protected UDPDiscoveryService service = null;
private String multicastAddressString = "";
private int multicastPort = 0;
private ICompositeCacheManager cacheMgr;
private boolean shutdown = false;
/**
* Constructor for the LateralUDPReceiver object.
* <p>
* We determine out own host using InetAddress
*
* @param service
* @param multicastAddressString
* @param multicastPort
* @param cacheMgr
* @exception IOException
*/
public UDPDiscoveryReceiver( UDPDiscoveryService service, String multicastAddressString, int multicastPort,
ICompositeCacheManager cacheMgr )
throws IOException
{
this.service = service;
this.multicastAddressString = multicastAddressString;
this.multicastPort = multicastPort;
this.cacheMgr = cacheMgr;
// create a small thread pool to handle a barage
pooledExecutor = new PooledExecutor( new BoundedBuffer( 100 ), maxPoolSize );
pooledExecutor.discardOldestWhenBlocked();
//pooledExecutor.setMinimumPoolSize(1);
pooledExecutor.setThreadFactory( new MyThreadFactory() );
if ( log.isInfoEnabled() )
{
log.info( "constructing listener, [" + this.multicastAddressString + ":" + this.multicastPort + "]" );
}
try
{
createSocket( this.multicastAddressString, this.multicastPort );
}
catch ( IOException ioe )
{
// consider eatign this so we can go on, or constructing the socket
// later
throw ioe;
}
}
/**
* Creates the socket for this class.
*
* @param multicastAddressString
* @param multicastPort
* @throws IOException
*/
private void createSocket( String multicastAddressString, int multicastPort )
throws IOException
{
try
{
m_socket = new MulticastSocket( multicastPort );
m_socket.joinGroup( InetAddress.getByName( multicastAddressString ) );
}
catch ( IOException e )
{
log.error( "Could not bind to multicast address [" + multicastAddressString + ":" + multicastPort + "]", e );
throw e;
}
}
/**
* Highly unreliable. If it is processing one message while another comes in ,
* the second message is lost. This is for low concurency peppering.
*
* @return the object message
* @throws IOException
*/
public Object waitForMessage()
throws IOException
{
final DatagramPacket packet = new DatagramPacket( m_buffer, m_buffer.length );
Object obj = null;
try
{
m_socket.receive( packet );
final ByteArrayInputStream byteStream = new ByteArrayInputStream( m_buffer, 0, packet.getLength() );
final ObjectInputStream objectStream = new ObjectInputStream( byteStream );
obj = objectStream.readObject();
}
catch ( Exception e )
{
log.error( "Error receving multicast packet", e );
}
return obj;
}
/** Main processing method for the LateralUDPReceiver object */
public void run()
{
try
{
while ( !shutdown )
{
Object obj = waitForMessage();
// not thread safe, but just for debugging
cnt++;
if ( log.isDebugEnabled() )
{
log.debug( getCnt() + " messages received." );
}
UDPDiscoveryMessage message = null;
try
{
message = (UDPDiscoveryMessage) obj;
// check for null
if ( message != null )
{
MessageHandler handler = new MessageHandler( message );
pooledExecutor.execute( handler );
if ( log.isDebugEnabled() )
{
log.debug( "Passed handler to executor." );
}
}
else
{
log.warn( "message is null" );
}
}
catch ( ClassCastException cce )
{
log.warn( "Received unknown message type " + cce.getMessage() );
}
} // end while
}
catch ( Exception e )
{
log.error( "Unexpected exception in UDP receiver.", e );
try
{
Thread.sleep( 100 );
// TODO consider some failure count so we don't do this
// forever.
}
catch ( Exception e2 )
{
log.error( "Problem sleeping", e2 );
}
}
return;
}
/**
* @param cnt
* The cnt to set.
*/
public void setCnt( int cnt )
{
this.cnt = cnt;
}
/**
* @return Returns the cnt.
*/
public int getCnt()
{
return cnt;
}
/**
* Separate thread run when a command comes into the UDPDiscoveryReceiver.
*/
public class MessageHandler
implements Runnable
{
private UDPDiscoveryMessage message = null;
/**
* @param message
*/
public MessageHandler( UDPDiscoveryMessage message )
{
this.message = message;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run()
{
// consider comparing ports here instead.
if ( message.getRequesterId() == LateralCacheInfo.listenerId )
{
if ( log.isDebugEnabled() )
{
log.debug( "from self" );
}
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "from another" );
log.debug( "Message = " + message );
}
// if this is a request message, have the service handle it and
// return
if ( message.getMessageType() == UDPDiscoveryMessage.REQUEST_BROADCAST )
{
if ( log.isDebugEnabled() )
{
log.debug( "Message is a Request Broadcase, will have the service handle it." );
}
service.serviceRequestBroadcast();
return;
}
try
{
// get a cache and add it to the no waits
// the add method should not add the same.
// we need the listener port from the original config.
ITCPLateralCacheAttributes lca = null;
if ( service.getTcpLateralCacheAttributes() != null )
{
lca = (ITCPLateralCacheAttributes) service.getTcpLateralCacheAttributes().copy();
}
else
{
lca = new TCPLateralCacheAttributes();
}
lca.setTransmissionType( LateralCacheAttributes.TCP );
lca.setTcpServer( message.getHost() + ":" + message.getPort() );
LateralTCPCacheManager lcm = LateralTCPCacheManager.getInstance( lca, cacheMgr );
ArrayList regions = message.getCacheNames();
if ( regions != null )
{
// for each region get the cache
Iterator it = regions.iterator();
while ( it.hasNext() )
{
String cacheName = (String) it.next();
try
{
ICache ic = lcm.getCache( cacheName );
if ( log.isDebugEnabled() )
{
log.debug( "Got cache, ic = " + ic );
}
// add this to the nowaits for this cachename
if ( ic != null )
{
service.addNoWait( (LateralCacheNoWait) ic );
if ( log.isDebugEnabled() )
{
log.debug( "Called addNoWait for cacheName " + cacheName );
}
}
}
catch ( Exception e )
{
log.error( "Problem creating no wait", e );
}
}
// end while
}
else
{
log.warn( "No cache names found in message " + message );
}
}
catch ( Exception e )
{
log.error( "Problem getting lateral maanger", e );
}
}
}
}
/**
* Allows us to set the daemon status on the executor threads
*
* @author aaronsm
*
*/
class MyThreadFactory
implements ThreadFactory
{
/*
* (non-Javadoc)
*
* @see EDU.oswego.cs.dl.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
*/
public Thread newThread( Runnable runner )
{
Thread t = new Thread( runner );
t.setDaemon( true );
t.setPriority( Thread.MIN_PRIORITY );
return t;
}
}
/*
* (non-Javadoc)
*
* @see org.apache.jcs.engine.behavior.ShutdownObserver#shutdown()
*/
public void shutdown()
{
try
{
shutdown = true;
m_socket.close();
pooledExecutor.shutdownNow();
}
catch ( Exception e )
{
log.error( "Problem closing socket" );
}
}
}
// end class