/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.restcomm.media.control.mgcp.tx;
import java.util.Iterator;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
import org.restcomm.media.concurrent.ConcurrentCyclicFIFO;
import org.restcomm.media.concurrent.ConcurrentMap;
import org.restcomm.media.control.mgcp.MgcpProvider;
import org.restcomm.media.control.mgcp.controller.CallManager;
import org.restcomm.media.control.mgcp.controller.naming.NamingTree;
import org.restcomm.media.scheduler.Clock;
import org.restcomm.media.scheduler.Scheduler;
/**
* Implements pool of transactions.
*
* @author yulian oifa
* @author Henrique Rosa (henrique.rosa@telestax.com)
*/
public class TransactionManager {
private static final Logger log = Logger.getLogger(TransactionManager.class);
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(1);
private static final int CACHE_SIZE = 5;
// core elements
private final Clock clock;
private final Scheduler scheduler;
protected CallManager callManager;
protected MgcpProvider provider;
protected NamingTree namingService;
// transactions
private final ConcurrentCyclicFIFO<Transaction> transactionPool;
private final ConcurrentMap<Transaction> activeTransactions;
// cache size in 100ms units
private ConcurrentCyclicFIFO<Transaction>[] cache = new ConcurrentCyclicFIFO[CACHE_SIZE];
private final Heartbeat cacheHeartbeat;
private ScheduledFuture<?> cacheHeartbeatFuture;
private int cleanIndex = 0;
/**
* Creates new transaction's pool.
*
* @param clock The wall clock of the server.
* @param scheduler The job scheduler
* @param size The size of the pool.
*/
public TransactionManager(Clock clock, Scheduler scheduler, int size) {
// core elements
this.clock = clock;
this.scheduler = scheduler;
// transactions
this.transactionPool = new ConcurrentCyclicFIFO<Transaction>();
this.activeTransactions = new ConcurrentMap<Transaction>();
for (int i = 0; i < size; i++) {
this.transactionPool.offer(new Transaction(this));
}
// cache
for (int i = 0; i < CACHE_SIZE; i++) {
this.cache[i] = new ConcurrentCyclicFIFO<Transaction>();
}
this.cacheHeartbeat = new Heartbeat();
}
public void start() {
// Schedule heart beat task with delays of 20ms between executions
this.cacheHeartbeatFuture = scheduler.scheduleWithFixedDelay(this.cacheHeartbeat, 20, 20, TimeUnit.MILLISECONDS);
}
/**
* Associates endpoint naming service.
*
* @param namingService the endpoint naming service.
*/
public void setNamingService(NamingTree namingService) {
this.namingService = namingService;
}
/**
* Gets the access to the scheduler.
*
* @return job scheduler.
*/
public Scheduler scheduler() {
return scheduler;
}
/**
* Provides access to the wall clock.
*
* @return time measured by wall clock.
*/
public long getTime() {
return this.clock.getTime();
}
public void setCallManager(CallManager callManager) {
this.callManager = callManager;
}
/**
* Assigns MGCP provider.
*
* @param provider mgcp provider instance
*/
public void setMgcpProvider(MgcpProvider provider) {
this.provider = provider;
}
/**
* Find active transaction with specified identifier.
*
* @param id the transaction identifier.
* @return transaction object or null if does not exist.
*/
public Transaction find(int id) {
Transaction currTransaction=activeTransactions.get(id);
return currTransaction;
}
/**
* Finds a transaction by number (rather than unique transaction ID) in the
* active transaction pool.
*
* @param transactionNumber
* the number of the transaction to look for
* @return The transaction with the desired number. Returns null if none
* matches criteria.
* @author hrosa
*/
public Transaction findByTransactionNumber(int transactionNumber) {
Iterator<Transaction> transactions = this.activeTransactions.valuesIterator();
while (transactions.hasNext()) {
Transaction transaction = transactions.next();
if (transaction.getId() == transactionNumber) {
return transaction;
}
}
return null;
}
public Transaction allocateNew(int id) {
Transaction t=begin(ID_GENERATOR.getAndIncrement());
if(t!=null) {
t.id=id;
}
return t;
}
/**
* Starts new transaction.
*
* @param id the identifier of new transaction.
* @return the object which represents transaction.
*/
private Transaction begin(int id) {
Transaction t = transactionPool.poll();
if (t == null) {
t=new Transaction(this);
}
t.uniqueId = id;
activeTransactions.put(t.uniqueId,t);
return t;
}
/**
* Terminates active transaction.
*
* @param t the transaction to be terminated
*/
protected void terminate(Transaction t) {
cache[cleanIndex].offer(t);
}
/**
* Generates unique transaction identifier.
*
* @return unique integer identifier.
*/
protected int nextID() {
return ID_GENERATOR.incrementAndGet();
}
/**
* Gets the remainder of unused transaction objects.
* Used for test purpose.
*
* @return the number of available transaction objects.
*/
protected int remainder() {
return transactionPool.size();
}
private class Heartbeat implements Runnable {
private int queueToClean;
public Heartbeat() {
super();
}
@Override
public void run() {
this.queueToClean = (cleanIndex + 1) % CACHE_SIZE;
Transaction current = cache[queueToClean].poll();
while (current != null) {
activeTransactions.remove(current.uniqueId);
current.id = 0;
current.uniqueId = 0;
current.completed = false;
transactionPool.offer(current);
current = cache[queueToClean].poll();
}
cleanIndex = (cleanIndex + 1) % CACHE_SIZE;
}
}
}