package org.jdiameter.server.impl;
import org.jdiameter.api.*;
import org.jdiameter.client.api.IMessage;
import org.jdiameter.client.api.ISessionFactory;
import org.jdiameter.client.api.fsm.EventTypes;
import org.jdiameter.client.api.fsm.IFsmFactory;
import org.jdiameter.client.api.io.IConnection;
import org.jdiameter.client.api.io.IConnectionListener;
import org.jdiameter.client.api.io.TransportException;
import org.jdiameter.client.api.parser.IMessageParser;
import org.jdiameter.client.impl.controller.PeerTableImpl;
import org.jdiameter.server.api.IMutablePeerTable;
import org.jdiameter.server.api.INetwork;
import org.jdiameter.server.api.IOverloadManager;
import org.jdiameter.server.api.IPeer;
import org.jdiameter.server.api.io.INetWorkConnectionListener;
import org.jdiameter.server.api.io.INetWorkGuard;
import org.jdiameter.server.api.io.ITransportLayerFactory;
import org.jdiameter.server.impl.helpers.EmptyConfiguration;
import static org.jdiameter.server.impl.helpers.Parameters.*;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.UnknownServiceException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.jdiameter.api.Network;
public class MutablePeerTableImpl extends PeerTableImpl implements IMutablePeerTable, ConfigurationListener {
private static final int MAX_DUPLICATE_ANSWERS = 5000;
private static final int CONN_INVALIDATE_PERIOD = 60000;
protected Configuration config;
protected ISessionFactory sessionFactory;
protected IFsmFactory fsmFactory;
protected ITransportLayerFactory transportFactory;
protected IMessageParser parser;
protected org.jdiameter.server.api.IRouter router;
protected boolean duplicateProtection = false;
protected long duplicateTimer;
protected ScheduledExecutorService dupliocationScheduler = null;
protected ScheduledFuture duplicationHandler = null;
protected ConcurrentHashMap<String, StorageEntry> storageAnswers = new ConcurrentHashMap<String, StorageEntry>();
protected boolean isAcceptUndefinedPeer = false;
private ConcurrentHashMap<String, IConnection> incConnections;
private ScheduledExecutorService connScheduler;
private ScheduledFuture connHandler;
protected INetWorkGuard networkGuard;
protected INetwork network;
protected Set<URI> predefinedPeerTable;
protected IOverloadManager ovrManager;
protected ScheduledExecutorService overloadScheduler = null;
protected ScheduledFuture overloadHandler = null;
protected PeerTableListener peerTableListener = null;
protected class StorageEntry {
private String duplicationKey;
private long time = System.currentTimeMillis();
private IMessage answer;
public StorageEntry(IMessage message) {
answer = message;
duplicationKey = message.getDuplicationKey();
}
public IMessage getMessage() {
return answer;
}
public long getTime() {
return time;
}
public String getDuplicationKey() {
return duplicationKey;
}
}
public MutablePeerTableImpl(Configuration config, MetaData metaData, org.jdiameter.server.api.IRouter router,
ISessionFactory sessionFactory, IFsmFactory fsmFactory, ITransportLayerFactory trFactory,
IMessageParser parser, INetwork network, IOverloadManager ovrManager) {
this.metaData = metaData;
this.config = config;
this.router = router;
this.sessionFactory = sessionFactory;
this.fsmFactory = fsmFactory;
this.transportFactory = trFactory;
this.parser = parser;
this.network = network;
this.ovrManager = ovrManager;
this.network.setPeerManager(this);
this.isAcceptUndefinedPeer = config.getBooleanValue(AcceptUndefinedPeer.ordinal(), false);
this.duplicateProtection = config.getBooleanValue(DuplicateProtection.ordinal(), (Boolean) DuplicateProtection.defValue());
if (this.duplicateProtection) {
this.duplicateTimer = config.getLongValue(DuplicateTimer.ordinal(), (Long) DuplicateTimer.defValue());
}
if (predefinedPeerTable == null) {
predefinedPeerTable = new CopyOnWriteArraySet<URI>();
}
if (config instanceof MutableConfiguration) {
((MutableConfiguration) config).addChangeListener(this);
}
init(router, config, metaData, fsmFactory, transportFactory, parser);
}
protected Peer createPeer(int rating, String uri, String ip, String portRange, MetaData metaData, Configuration globalConfig, Configuration peerConfig, IFsmFactory fsmFactory,
org.jdiameter.client.api.io.ITransportLayerFactory transportFactory, IMessageParser parser) throws InternalException, TransportException, URISyntaxException, UnknownServiceException {
if (predefinedPeerTable == null) {
predefinedPeerTable = new CopyOnWriteArraySet<URI>();
}
predefinedPeerTable.add(new URI(uri));
if (peerConfig.getBooleanValue(PeerAttemptConnection.ordinal(), false)) {
return newPeerInstance(rating, new URI(uri), ip, portRange, metaData, globalConfig, peerConfig, fsmFactory, (org.jdiameter.server.api.io.ITransportLayerFactory)transportFactory, parser, true, null);
}
else {
return null;
}
}
protected IPeer newPeerInstance(int rating, URI uri, String ip, String portRange, MetaData metaData, Configuration globalConfig, Configuration peerConfig, IFsmFactory fsmFactory,
org.jdiameter.server.api.io.ITransportLayerFactory transportFactory, IMessageParser parser,
boolean attCnn, IConnection connection ) throws URISyntaxException, UnknownServiceException, InternalException, TransportException {
return new org.jdiameter.server.impl.PeerImpl(this, rating, uri, ip, portRange, (org.jdiameter.server.api.IMetaData) metaData,
globalConfig, peerConfig, sessionFactory, fsmFactory, transportFactory, parser, network, ovrManager, attCnn, connection
);
}
public void setPeerTableListener(PeerTableListener peerTableListener) {
this.peerTableListener = peerTableListener;
}
public boolean elementChanged(int i, Object data) {
Configuration newConf = (Configuration) data;
stopTimeOut = newConf.getLongValue(StopTimeOut.ordinal(), (Long) StopTimeOut.defValue());
duplicateTimer = newConf.getLongValue(DuplicateTimer.ordinal(), (Long) DuplicateTimer.defValue());
isAcceptUndefinedPeer = newConf.getBooleanValue(AcceptUndefinedPeer.ordinal(), false);
return true;
}
public boolean isDuplicateProtection() {
return duplicateProtection;
}
public void start() throws IllegalDiameterStateException, IOException { // TODO: use parent method
if (peerTaskExecutor.isShutdown()) {
peerTaskExecutor = Executors.newCachedThreadPool();
}
router.start();
// Start overload manager
overloadScheduler = Executors.newScheduledThreadPool(1);
Runnable overloadTask = new Runnable() {
public void run() {
if (ovrManager != null) {
for (Peer p : peerTable.values()) {
((IPeer) p).notifyOvrManager(ovrManager);
}
}
}
};
overloadHandler = overloadScheduler.scheduleAtFixedRate(overloadTask, 0, 1, TimeUnit.SECONDS);
// Start duplication protection procedure
if (duplicateProtection) {
dupliocationScheduler = Executors.newScheduledThreadPool(1);
Runnable duplicateTask = new Runnable() {
public void run() {
long now = System.currentTimeMillis();
for (StorageEntry s : storageAnswers.values()) {
if (s != null && s.getTime() + duplicateTimer <= now) {
storageAnswers.remove(s.getDuplicationKey());
}
}
}
};
duplicationHandler = dupliocationScheduler.scheduleAtFixedRate(duplicateTask, duplicateTimer, duplicateTimer, TimeUnit.MILLISECONDS);
}
//
connScheduler = Executors.newScheduledThreadPool(1);
Runnable connectionCheckTask = new Runnable() {
public void run() {
Map<String, IConnection> connections = getIncConnections();
for (IConnection connection : connections.values()) {
if (System.currentTimeMillis() - connection.getCreatedTime() <= CONN_INVALIDATE_PERIOD) {
logger.debug("External connection released by timeout {}", connection);
try {
connection.remAllConnectionListener();
connection.release();
}
catch (IOException e) {
logger.debug("Can not release connection", e);
}
connections.remove(connection.getKey());
}
}
}
};
connHandler = connScheduler.scheduleAtFixedRate(connectionCheckTask, CONN_INVALIDATE_PERIOD, CONN_INVALIDATE_PERIOD, TimeUnit.MILLISECONDS);
// Start server socket
try {
networkGuard = createNetWorkGuard(transportFactory);
}
catch (TransportException e) {
logger.debug("Can not create server socket", e);
}
// Connect to predefined peers
for (Peer p : peerTable.values()) {
try {
p.connect();
}
catch (Exception e) {
logger.warn("Can not start connect procedure for peer {}", p, e);
}
}
isStarted = true;
}
public Set<URI> getPredefinedPeerTable() {
return predefinedPeerTable;
}
public ConcurrentHashMap<String, IConnection> getIncConnections() {
if (incConnections == null) {
incConnections = new ConcurrentHashMap<String, IConnection>();
}
return incConnections;
}
private final Object regLock = new Object();
private INetWorkGuard createNetWorkGuard(final ITransportLayerFactory transportFactory) throws TransportException {
return transportFactory.createNetWorkGuard(
metaData.getLocalPeer().getIPAddresses()[0],
metaData.getLocalPeer().getUri().getPort(),
new INetWorkConnectionListener() {
public void newNetWorkConnection(final IConnection connection) {
synchronized (regLock) {
final IConnectionListener listener = new IConnectionListener() {
public void connectionOpened(String connKey) {
logger.debug("Connection {} opened", connKey);
}
public void connectionClosed(String connKey, List notSended) {
logger.debug("Connection {} closed", connKey);
unregister(true);
}
public void messageReceived(String connKey, IMessage message) {
if (message.isRequest() && message.getCommandCode() == Message.CAPABILITIES_EXCHANGE_REQUEST) {
connection.remConnectionListener(this);
IPeer peer = null;
String host;
try {
host = message.getAvps().getAvp(Avp.ORIGIN_HOST).getOctetString();
}
catch (AvpDataException e) {
logger.warn("Can not find ORIG_HOST avp in CER", e);
unregister(true);
return;
}
boolean foundInpredefinedTable = false;
// find into predefined table
for (URI uri : predefinedPeerTable) {
if (uri.getFQDN().equals(host)) {
peer = (IPeer) peerTable.get(uri);
foundInpredefinedTable = true; // found but not init
break;
}
}
// find in peertable for peer already connected to server but not removed
if (peer == null) {
for (URI uri : peerTable.keySet()) {
if (uri.getFQDN().equals(host)) {
peer = (IPeer) peerTable.get(uri);
break;
}
}
}
if (peer != null) {
peer.addIncomingConnection(connection);
try {
peer.handleMessage(message.isRequest() ? EventTypes.CER_EVENT : EventTypes.CER_EVENT, message, connKey);
}
catch (Exception e) {
logger.debug("Can not process CER message", e);
}
}
else {
if (isAcceptUndefinedPeer || foundInpredefinedTable) {
try {
int port = connection.getRemotePort();
boolean hostAsUri = config.getBooleanValue(UseUriAsFqdn.ordinal(), (Boolean) UseUriAsFqdn.defValue());
URI uri;
if (hostAsUri || host.startsWith("aaa://")) {
uri = new URI(host);
}
else {
uri = new URI("aaa://" + host + ":" + port);
}
peer = newPeerInstance(0, uri, connection.getRemoteAddress().getHostAddress(), null, metaData,
config, null, fsmFactory, transportFactory, parser, false, connection);
appendPeerToPeerTable(peer);
peer.handleMessage(message.isRequest() ? EventTypes.CER_EVENT : EventTypes.CER_EVENT, message, connKey);
}
catch (Exception e) {
logger.warn("Can not create peer", e);
unregister(true);
}
}
else {
logger.info("Skip anonymous connection {}", connection.toString());
unregister(true);
}
}
}
else {
logger.debug("Unknown message {} by connection {}", new Object[]{message, connKey});
unregister(true);
}
}
public void internalError(String connKey, IMessage message, TransportException cause) {
logger.debug("Connection {} internalError {}", new Object[]{connKey, cause});
unregister(true);
}
public void unregister(boolean release) {
getIncConnections().remove(connection.getKey());
connection.remConnectionListener(this);
if (release && connection.isConnected()) {
try {
connection.release();
}
catch (IOException e) {
logger.debug("Can not release connection {}", connection);
}
}
}
};
getIncConnections().put(connection.getKey(), connection);
connection.addConnectionListener(listener);
}
}
}
);
}
private void appendPeerToPeerTable(IPeer peer) {
peerTable.put(peer.getUri(), peer);
if (peerTableListener != null) {
peerTableListener.peerAccepted(peer);
}
}
public void stopping() {
super.stopping();
if (networkGuard != null) {
networkGuard.destroy();
networkGuard = null;
}
//
if (overloadScheduler != null) {
Executors.unconfigurableScheduledExecutorService(overloadScheduler);
overloadScheduler = null;
overloadHandler.cancel(true);
overloadHandler = null;
}
//
if (dupliocationScheduler != null) {
Executors.unconfigurableScheduledExecutorService(dupliocationScheduler);
dupliocationScheduler = null;
}
if (duplicationHandler != null) {
duplicationHandler.cancel(true);
duplicationHandler = null;
}
//
if (connScheduler != null) {
Executors.unconfigurableScheduledExecutorService(connScheduler);
connScheduler = null;
}
if (connHandler != null) {
connHandler.cancel(true);
connHandler = null;
}
//
storageAnswers.clear();
}
public Peer addPeer(URI peerURI, String realm, boolean connecting) {
try {
Configuration peerConfig = null;
Configuration[] peers = config.getChildren(PeerTable.ordinal());
// find peer config
for (Configuration c : peers)
if (peerURI.getFQDN().equals(c.getStringValue(PeerName.ordinal(), ""))) {
peerConfig = c;
break;
}
if (peerConfig == null) {
peerConfig = new EmptyConfiguration(false).add(PeerAttemptConnection, connecting);
}
IPeer peer = (IPeer) createPeer(0, peerURI.getFQDN(), null, null, metaData, config, peerConfig, fsmFactory, transportFactory, parser);
if (peer == null) return null;
peer.setRealm(realm);
appendPeerToPeerTable(peer);
boolean found = false;
for (Realm r : router.getRealms()) {
if (r.getName().equals(realm)) {
r.addPeerName(peerURI.getFQDN());
found = true;
break;
}
}
if (!found) {
throw new IllegalArgumentException("Incorrect realm name");
}
if (connecting) {
peer.connect();
}
return peer;
}
catch(Exception e) {
logger.debug("Can not add peer", e);
return null;
}
}
public Set<Realm> getAllRealms() {
return router.getRealms();
}
public Peer removePeer(String host) {
try {
URI peerUri = null;
for (URI u : peerTable.keySet()) {
if (u.getFQDN().equals(host)) {
peerUri = u;
peerTable.get(u).disconnect();
}
}
if (peerUri != null) {
predefinedPeerTable.remove(peerUri);
Peer removedPeer = peerTable.remove(peerUri);
if (peerTableListener != null) {
peerTableListener.peerRemoved(removedPeer);
}
return removedPeer;
}
else {
return null;
}
} catch (Exception e) {
logger.debug("Can not remove peer", e);
return null;
}
}
public Statistic getStatistic(String name) {
for (Peer p : peerTable.values()) {
if (p.getUri().getFQDN().equals(name)) {
return ((IPeer) p).getStatistic();
}
}
return null;
}
public IMessage isDuplicate(IMessage request) {
String key = request.getDuplicationKey();
if (key != null && storageAnswers != null) {
StorageEntry entry = storageAnswers.get(key);
return entry != null ? entry.getMessage() : null;
}
return null;
}
public void saveToDuplicate(String key, IMessage answer) {
if (storageAnswers != null && storageAnswers.size() < MAX_DUPLICATE_ANSWERS) {
if (key != null) {
storageAnswers.put(key, new StorageEntry(answer));
}
}
}
public ISessionFactory getSessionFactory() {
return sessionFactory;
}
public boolean isWrapperFor(Class<?> aClass) throws InternalException {
boolean isWrapp = super.isWrapperFor(aClass);
return aClass == MutablePeerTable.class || aClass == Network.class || isWrapp;
}
public <T> T unwrap(Class<T> aClass) throws InternalException {
if (aClass == MutablePeerTable.class) {
return (T) assembler.getComponentInstance(aClass);
}
if (aClass == Network.class) {
return (T) assembler.getComponentInstance(aClass);
}
return null;
}
}