/*
* File Name : JainMgcpStackImpl.java
*
* The JAIN MGCP API implementaion.
*
* The source code contained in this file is in in the public domain.
* It can be used in any project or product without prior permission,
* license or royalty payments. There is NO WARRANTY OF ANY KIND,
* EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION,
* THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
* AND DATA ACCURACY. We do not warrant or make any representations
* regarding the use of the software or the results thereof, including
* but not limited to the correctness, accuracy, reliability or
* usefulness of the software.
*/
package org.mobicents.mgcp.stack;
import jain.protocol.ip.mgcp.CreateProviderException;
import jain.protocol.ip.mgcp.DeleteProviderException;
import jain.protocol.ip.mgcp.JainMgcpProvider;
import jain.protocol.ip.mgcp.JainMgcpStack;
import jain.protocol.ip.mgcp.OAM_IF;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
import org.mobicents.mgcp.stack.handlers.EndpointHandlerManager;
import org.mobicents.mgcp.stack.parser.UtilsFactory;
import org.mobicents.mgcp.stack.utils.*;
/**
*
* @author Oleg Kulikov
* @author Pavel Mitrenko
*/
public class JainMgcpStackImpl extends Thread implements JainMgcpStack, EndpointHandlerManager, OAM_IF {
// Static variables from properties files
/**
* Defines how many executors will work on event delivery
*/
public static final String _EXECUTOR_TABLE_SIZE = "executorTableSize";
/**
* Defines how many message can be stored in queue before new ones are
* discarded.
*/
public static final String _EXECUTOR_QUEUE_SIZE = "executorQueueSize";
public static final String _MESSAGE_READER_THREAD_PRIORITY = "messageReaderThreadPriority";
public static final String _MESSAGE_DISPATCHER_THREAD_PRIORITY = "messageDispatcherThreadPriority";
public static final String _MESSAGE_EXECUTOR_THREAD_PRIORITY = "messageExecutorThreadPriority";
private static final Logger logger = Logger.getLogger(JainMgcpStackImpl.class);
private static final String propertiesFileName = "mgcp-stack.properties";
private String protocolVersion = "1.0";
protected int port = 2727;
private DatagramSocket socket;
private InetAddress localAddress = null;
private boolean stopped = true;
private int executorTableSize = 200;
private int executorQueueSize = -1;
private int messageReaderThreadPriority = Thread.MAX_PRIORITY;
private int messageDispatcherThreadPriority = Thread.NORM_PRIORITY + 2; // 7
private int messageExecutorThreadPriority = Thread.MIN_PRIORITY;
private ThreadPoolQueueExecutor[] executors = null;
private int executorPosition = 0;
private UtilsFactory utilsFactory = null;
private PacketRepresentationFactory prFactory = null;
private EndpointHandlerFactory ehFactory = null;
//Should we ever get data more than 5000 bytes?
private static final int BUFFER_SIZE = 5000;
private byte[] buffer = new byte[BUFFER_SIZE];
private DatagramPacket packet = null;
// For now we have only one provider/delete prvider method wont work.
public JainMgcpStackProviderImpl provider = null;
/**
* holds current active transactions (RFC 3435 [$3.2.1.2]: for tx sent &
* received).
*
*/
private ConcurrentHashMap<Integer, TransactionHandler> localTransactions = new ConcurrentHashMap<Integer, TransactionHandler>();
private ConcurrentHashMap<Integer, Integer> remoteTxToLocalTxMap = new ConcurrentHashMap<Integer, Integer>();
private ConcurrentHashMap<Integer, TransactionHandler> completedTransactions = new ConcurrentHashMap<Integer, TransactionHandler>();
// private ConcurrentSkipListMap<String, EndpointHandler> endpointHandlers =
// new ConcurrentSkipListMap<String, EndpointHandler>(new
// StringComparator());
private SortedMap<String, EndpointHandler> endpointHandlers = Collections
.synchronizedSortedMap(new TreeMap<String, EndpointHandler>(new StringComparator()));
// Queue part
protected LinkedList<PacketRepresentation> rawQueue = new LinkedList<PacketRepresentation>();
protected ThreadPoolQueueExecutor eventSchedulerExecutor = null;
protected MessageHandler messageHandler = null;
// protected Timer tt=new Timer();
public void printStats() {
System.out.println("endpointHandlers size = " + endpointHandlers.size());
System.out.println("localTransactions size = " + localTransactions.size());
System.out.println("remoteTxToLocalTxMap size = " + remoteTxToLocalTxMap.size());
System.out.println("completedTransactions size = " + completedTransactions.size());
}
// Defualt constructor for TCK
public JainMgcpStackImpl() {
}
/** Creates a new instance of JainMgcpStackImpl */
public JainMgcpStackImpl(InetAddress localAddress, int port) {
this.localAddress = localAddress;
this.port = port;
}
private void init() {
readProperties();
initExecutors();
if (socket == null) {
while (true) {
try {
InetSocketAddress bindAddress = new InetSocketAddress(this.localAddress, this.port);
socket = new DatagramSocket(bindAddress);
this.localAddress = socket.getLocalAddress();
logger.info("Jain Mgcp stack bound to IP " + this.localAddress + " and UDP port " + this.port);
// This is for TCK don't remove
System.out.println("Jain Mgcp stack bound to IP " + this.localAddress + " and UDP port "
+ this.port);
break;
} catch (SocketException e) {
logger.error(e);
throw new RuntimeException("Failed to find a local port to bound stack");
}
}
}
stopped = false;
if (logger.isDebugEnabled()) {
logger.debug("Starting main thread " + this);
}
this.provider = new JainMgcpStackProviderImpl(this);
this.utilsFactory = new UtilsFactory(25);
this.prFactory = new PacketRepresentationFactory(50, BUFFER_SIZE);
this.messageHandler = new MessageHandler(this);
this.eventSchedulerExecutor.execute(new EventSchedulerTask());
this.setPriority(this.messageReaderThreadPriority);
// So stack does not die
this.setDaemon(false);
this.ehFactory = new EndpointHandlerFactory(50, this);
start();
}
private void readProperties() {
try {
Properties props = new Properties();
InputStream is = this.getClass().getResourceAsStream(this.propertiesFileName);
if (is == null) {
logger.error("Failed to locate properties file, using default values");
return;
}
props.load(is);
String val = null;
val = props.getProperty(_EXECUTOR_TABLE_SIZE, "" + executorTableSize);
this.executorTableSize = Integer.parseInt(val);
val = null;
val = props.getProperty(_EXECUTOR_QUEUE_SIZE, "" + executorQueueSize);
this.executorQueueSize = Integer.parseInt(val);
val = null;
val = props.getProperty(_MESSAGE_READER_THREAD_PRIORITY, "" + this.messageReaderThreadPriority);
this.messageReaderThreadPriority = Integer.parseInt(val);
val = null;
val = props.getProperty(_MESSAGE_DISPATCHER_THREAD_PRIORITY, "" + this.messageDispatcherThreadPriority);
this.messageDispatcherThreadPriority = Integer.parseInt(val);
val = null;
val = props.getProperty(_MESSAGE_EXECUTOR_THREAD_PRIORITY, "" + this.messageExecutorThreadPriority);
this.messageExecutorThreadPriority = Integer.parseInt(val);
val = null;
logger.info(this.propertiesFileName + " read successfully! \nexecutorTableSize = " + this.executorTableSize
+ "\nexecutorQueueSize = " + this.executorQueueSize + "\nmessageReaderThreadPriority = "
+ this.messageReaderThreadPriority + "\nmessageDispatcherThreadPriority = "
+ this.messageDispatcherThreadPriority + "\nmessageExecutorThreadPriority = "
+ this.messageExecutorThreadPriority);
} catch (Exception e) {
logger.error("Failed to read properties file due to some error, using defualt values!!!!");
}
}
private void initExecutors() {
this.executors = new ThreadPoolQueueExecutor[this.executorTableSize];
ThreadFactoryImpl th = new ThreadFactoryImpl();
th.setPriority(this.messageExecutorThreadPriority);
th.setDaemonFactory(true);
for (int i = 0; i < this.executors.length; i++) {
if (executorQueueSize > 0)
this.executors[i] = new ThreadPoolQueueExecutor(1, 1, new LinkedBlockingQueue<Runnable>(
executorQueueSize));
else
this.executors[i] = new ThreadPoolQueueExecutor(1, 1, new LinkedBlockingQueue<Runnable>());
this.executors[i].setThreadFactory(th);
}
// if (this.incomingDataBufferSize > 0)
// this.eventSchedulerExecutor = new ThreadPoolQueueExecutor(1, 1, new
// LinkedBlockingQueue<Runnable>(
// incomingDataBufferSize));
// else
this.eventSchedulerExecutor = new ThreadPoolQueueExecutor(1, 1, new LinkedBlockingQueue<Runnable>());
th = new ThreadFactoryImpl();
th.setPriority(this.messageDispatcherThreadPriority);
th.setDaemonFactory(true);
eventSchedulerExecutor.setThreadFactory(th);
}
private void terminateExecutors() {
this.eventSchedulerExecutor.shutdown();
for (ThreadPoolQueueExecutor tpqe : this.executors) {
if (tpqe != null) {
tpqe.shutdown();
}
}
}
/**
* Closes the stack and it's underlying resources.
*/
public void close() {
stopped = true;
try {
if (logger.isDebugEnabled()) {
logger.debug("Closing socket");
}
if (socket != null) {
socket.close();
}
// jainMgcpStackImplPool.shutdown();
terminateExecutors();
} catch (Exception e) {
logger.warn("Could not gracefully close socket", e);
}
}
public JainMgcpProvider createProvider() throws CreateProviderException {
if (this.provider != null) {
throw new CreateProviderException(
"Provider already created. Only 1 provider can be created. Delete the first and then re-create");
}
init();
return this.provider;
}
public void deleteProvider(JainMgcpProvider provider) throws DeleteProviderException {
if (this.provider == null) {
throw new DeleteProviderException("No Provider exist.");
}
this.close();
this.provider = null;
}
public void setPort(int port) {
this.port = port;
}
public int getPort() {
return port;
}
public UtilsFactory getUtilsFactory() {
return this.utilsFactory;
}
public void setUtilsFactory(UtilsFactory utilsFactory) {
this.utilsFactory = utilsFactory;
}
public InetAddress getAddress() {
if (this.localAddress != null) {
return this.localAddress;
} else {
return null;
}
}
public String getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(String protocolVersion) {
this.protocolVersion = protocolVersion;
}
protected synchronized void send(DatagramPacket packet) {
try {
// if (logger.isDebugEnabled()) {
// logger.debug("Sending " + packet.getLength() + " bytes to " +
// packet.getAddress() + " port = "
// + packet.getPort());
// }
socket.send(packet);
} catch (IOException e) {
logger.error("I/O Exception uccured, caused by", e);
}
}
public boolean isRequest(String header) {
return header.matches("[\\w]{4}(\\s|\\S)*");
}
@Override
public void run() {
if (logger.isDebugEnabled()) {
logger.debug("MGCP stack started successfully on " + this.localAddress + ":" + this.port);
}
int length = 0;
while (!stopped) {
try {
packet = new DatagramPacket(buffer, buffer.length);
if (logger.isDebugEnabled()) {
logger.debug("Waiting for packet delivery");
}
socket.receive(packet);
} catch (IOException e) {
if (stopped) {
break;
}
logger.error("I/O exception occured:", e);
continue;
}
// uses now the actual data length from the DatagramPacket
// instead of the length of the byte[] buffer
length = packet.getLength();
synchronized (rawQueue) {
PacketRepresentation pr = this.prFactory.allocate();
System.arraycopy(packet.getData(), 0, pr.getRawData(), 0, length);
pr.setLength(length);
pr.setRemoteAddress(packet.getAddress());
pr.setRemotePort(packet.getPort());
rawQueue.add(pr);
rawQueue.notify();
}
}
if (logger.isDebugEnabled()) {
logger.debug("MGCP stack stopped gracefully on" + this.localAddress + ":" + this.port);
}
}
protected class EventSchedulerTask implements Runnable {
boolean runSwitch = true;
public void run() {
synchronized (rawQueue) {
while (runSwitch) {
if (rawQueue.size() == 0) {
try {
rawQueue.wait();
} catch (InterruptedException e) {
logger.error(e);
return;
}
}
PacketRepresentation pr = rawQueue.remove();
messageHandler.scheduleMessages(pr);
}
}
}
public boolean isRunSwitch() {
return runSwitch;
}
public void setRunSwitch(boolean runSwitch) {
this.runSwitch = runSwitch;
}
}
public synchronized EndpointHandler getEndpointHandler(String endpointId, boolean useFakeOnWildcard) {
EndpointHandler eh = null;
String _endpointId = endpointId.intern();
// if (logger.isDebugEnabled()) {
// for (String key : endpointHandlers.keySet()) {
// logger.debug("-------------" + this.localAddress + ":" + this.port +
// "--------------\n" + endpointId
// + "\n" + key + "\n" + (endpointId.equals(key)) + "\n"
// + endpointHandlers.containsKey(_endpointId) +
// "\n---------------------------");
// }
// }
// In case of fake we always create new EH
if (useFakeOnWildcard) {
// eh = new EndpointHandler(this, _endpointId);
eh = this.ehFactory.allocate(_endpointId);
eh.setUseFake(true);
endpointHandlers.put(eh.getFakeId(), eh);
} else if (!endpointHandlers.containsKey(_endpointId)) {
// if (logger.isDebugEnabled()) {
// logger.debug("Adding endpoint handler on:" + this.localAddress +
// ":" + this.port + ", using fakeId["
// + useFakeOnWildcard + "] - " + _endpointId);
// }
// eh = new EndpointHandler(this, _endpointId);
eh = this.ehFactory.allocate(_endpointId);
endpointHandlers.put(_endpointId, eh);
} else {
// if (logger.isDebugEnabled()) {
// logger.debug("Fetching endpoint handler on: " + this.localAddress
// + ":" + this.port + " - "
// + _endpointId);
// }
eh = endpointHandlers.get(_endpointId);
}
// int count = 0;
// if (logger.isDebugEnabled())
// for (String key : endpointHandlers.keySet()) {
// logger.debug("----AA--[" + (count++) + "]-------" + this.localAddress
// + ":" + this.port
// + "-------------- " + key + ": " + endpointHandlers.get(key));
// }
return eh;
}
public synchronized void removeEndpointHandler(String endpointId) {
// System.out.println("Removing for EndpointId "+endpointId);
EndpointHandler eh = this.endpointHandlers.remove(endpointId.intern());
// System.out.println("Removed = " + eh + " size of
// this.endpointHandlers = " + this.endpointHandlers.size());
// if (logger.isDebugEnabled()) {
// logger.debug("Removing EH" + this.localAddress + ":" + this.port + ":
// for:" + endpointId + " = " + eh);
// }
}
public synchronized EndpointHandler switchMapping(String fakeId, String specificEndpointId) {
EndpointHandler eh = this.endpointHandlers.get(specificEndpointId);
if (eh == null) {
// Well this means we are first, noone has return this before us so
// we do the switch
eh = this.endpointHandlers.remove(fakeId);
this.endpointHandlers.put(specificEndpointId, eh);
eh.setUseFake(false);
eh = null;
}
// int count = 0;
// if (logger.isDebugEnabled())
// for (String key : endpointHandlers.keySet()) {
// logger.debug("----AS--[" + (count++) + "]-------" + this.localAddress
// + ":" + this.port
// + "-------------- " + key + ": " + endpointHandlers.get(key));
// }
return eh;
}
public ThreadPoolQueueExecutor getNextExecutor() {
return this.executors[(this.executorPosition++) % this.executorTableSize];
}
public Map<Integer, TransactionHandler> getLocalTransactions() {
return localTransactions;
}
public Map<Integer, Integer> getRemoteTxToLocalTxMap() {
return remoteTxToLocalTxMap;
}
public Map<Integer, TransactionHandler> getCompletedTransactions() {
return completedTransactions;
}
protected class StringComparator implements Comparator<String> {
public int compare(String o1, String o2) {
if (o1 == null)
return -1;
if (o2 == null)
return 1;
return o1.compareTo(o2);
}
}
static class ThreadFactoryImpl implements ThreadFactory {
final ThreadGroup group;
final AtomicInteger threadNumber = new AtomicInteger(1);
final String namePrefix;
protected int priority = Thread.NORM_PRIORITY;
protected boolean isDaemonFactory = false;
ThreadFactoryImpl() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "JainMgcpStackImpl-FixedThreadPool-" + "thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 5);
t.setDaemon(this.isDaemonFactory);
// if (t.getPriority() != Thread.NORM_PRIORITY)
// t.setPriority(Thread.NORM_PRIORITY);
t.setPriority(priority);
return t;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public boolean isDaemonFactory() {
return isDaemonFactory;
}
public void setDaemonFactory(boolean isDaemonFactory) {
this.isDaemonFactory = isDaemonFactory;
}
}
}