/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2016, TeleStax Inc. and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * * This file incorporates work covered by the following copyright and * permission notice: * * JBoss, Home of Professional Open Source * Copyright 2007-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.jdiameter.client.impl; import static org.jdiameter.client.impl.helpers.ExtensionPoint.ControllerLayer; import static org.jdiameter.client.impl.helpers.ExtensionPoint.StackLayer; import static org.jdiameter.client.impl.helpers.ExtensionPoint.TransportLayer; import static org.jdiameter.client.impl.helpers.Parameters.Assembler; import static org.jdiameter.common.api.concurrent.IConcurrentFactory.ScheduledExecServices.ProcessingMessageTimer; import java.io.IOException; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.jdiameter.api.AvpDataException; import org.jdiameter.api.BaseSession; import org.jdiameter.api.Configuration; import org.jdiameter.api.DisconnectCause; import org.jdiameter.api.IllegalDiameterStateException; import org.jdiameter.api.InternalException; import org.jdiameter.api.MetaData; import org.jdiameter.api.Mode; import org.jdiameter.api.NetworkReqListener; import org.jdiameter.api.Peer; import org.jdiameter.api.PeerState; import org.jdiameter.api.PeerTable; import org.jdiameter.api.RouteException; import org.jdiameter.api.SessionFactory; import org.jdiameter.api.app.StateChangeListener; import org.jdiameter.api.validation.Dictionary; import org.jdiameter.api.validation.ValidatorLevel; import org.jdiameter.client.api.IAssembler; import org.jdiameter.client.api.IContainer; import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.IMetaData; import org.jdiameter.client.api.StackState; import org.jdiameter.client.api.controller.IPeer; import org.jdiameter.client.api.controller.IPeerTable; import org.jdiameter.client.impl.helpers.Parameters; import org.jdiameter.common.api.concurrent.IConcurrentFactory; import org.jdiameter.common.api.data.ISessionDatasource; import org.jdiameter.common.api.statistic.IStatisticProcessor; import org.jdiameter.common.api.timer.ITimerFacility; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Use stack extension point * * @author erick.svenson@yahoo.com * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a> * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> */ public class StackImpl implements IContainer, StackImplMBean { private static final Logger log = LoggerFactory.getLogger(StackImpl.class); protected IAssembler assembler; protected IConcurrentFactory concurrentFactory; protected Configuration config; protected IPeerTable peerManager; protected StackState state = StackState.IDLE; protected Lock lock = new ReentrantLock(); /** * Use for processing request time-out tasks (for all active peers) */ protected ScheduledExecutorService scheduledFacility; @Override @SuppressWarnings("unchecked") public SessionFactory init(Configuration config) throws IllegalDiameterStateException, InternalException { lock.lock(); if (log.isInfoEnabled()) { log.info("(-)(-)(-)(-)(-) Starting " + VersionProperties.instance.getProperty("vendor") + " DIAMETER Stack v" + VersionProperties.instance.getProperty("version") + " (-)(-)(-)(-)(-)"); } try { if (state != StackState.IDLE) { throw new IllegalDiameterStateException(); } try { Class assemblerClass = Class.forName(config.getStringValue(Assembler.ordinal(), (String) Assembler.defValue())); assembler = (IAssembler) assemblerClass.getConstructor(Configuration.class).newInstance(config); // register common instances assembler.registerComponentInstance(this); assembler.registerComponentInstance(config); } catch (Exception e) { throw new InternalException(e); } this.config = config; this.concurrentFactory = assembler.getComponentInstance(IConcurrentFactory.class); try { Configuration[] dictionaryConfigs = config.getChildren(Parameters.Dictionary.ordinal()); // Initialize with default values String dictionaryClassName = (String) Parameters.DictionaryClass.defValue(); Boolean validatorEnabled = (Boolean) Parameters.DictionaryEnabled.defValue(); ValidatorLevel validatorSendLevel = ValidatorLevel.fromString((String) Parameters.DictionarySendLevel.defValue()); ValidatorLevel validatorReceiveLevel = ValidatorLevel.fromString((String) Parameters.DictionaryReceiveLevel.defValue()); if (dictionaryConfigs != null && dictionaryConfigs.length > 0) { Configuration dictionaryConfiguration = dictionaryConfigs[0]; dictionaryClassName = dictionaryConfiguration.getStringValue(Parameters.DictionaryClass.ordinal(), (String) Parameters.DictionaryClass.defValue()); validatorEnabled = dictionaryConfiguration.getBooleanValue(Parameters.DictionaryEnabled.ordinal(), (Boolean) Parameters.DictionaryEnabled.defValue()); validatorSendLevel = ValidatorLevel.fromString(dictionaryConfiguration.getStringValue(Parameters.DictionarySendLevel.ordinal(), (String) Parameters.DictionarySendLevel.defValue())); validatorReceiveLevel = ValidatorLevel.fromString(dictionaryConfiguration.getStringValue(Parameters.DictionaryReceiveLevel.ordinal(), (String) Parameters.DictionaryReceiveLevel.defValue())); } createDictionary(dictionaryClassName, validatorEnabled, validatorSendLevel, validatorReceiveLevel); } catch (Exception e) { throw new InternalException(e); } // create manager this.peerManager = assembler.getComponentInstance(IPeerTable.class); this.peerManager.setAssembler(assembler); this.state = StackState.CONFIGURED; } finally { lock.unlock(); } if (log.isInfoEnabled()) { log.info("(-)(-)(-)(-)(-) Started " + VersionProperties.instance.getProperty("vendor") + " DIAMETER Stack v" + VersionProperties.instance.getProperty("version") + " (-)(-)(-)(-)(-)"); } return assembler.getComponentInstance(SessionFactory.class); } private void createDictionary(String clazz, boolean validatorEnabled, ValidatorLevel validatorSendLevel, ValidatorLevel validatorReceiveLevel) throws InternalException { // Defer call to singleton DictionarySingleton.init(clazz, validatorEnabled, validatorSendLevel, validatorReceiveLevel); } @Override public SessionFactory getSessionFactory() throws IllegalDiameterStateException { if (state == StackState.CONFIGURED || state == StackState.STARTED) { // FIXME: When possible, get rid of IoC here. return assembler.getComponentInstance(SessionFactory.class); } else { throw new IllegalDiameterStateException(); } } @Override public Dictionary getDictionary() throws IllegalDiameterStateException { return DictionarySingleton.getDictionary(); } @Override public void start() throws IllegalDiameterStateException, InternalException { lock.lock(); try { if (state != StackState.STOPPED && state != StackState.CONFIGURED) { throw new IllegalDiameterStateException(); } scheduledFacility = concurrentFactory.getScheduledExecutorService(ProcessingMessageTimer.name()); assembler.getComponentInstance(ISessionDatasource.class).start(); assembler.getComponentInstance(IStatisticProcessor.class).start(); assembler.getComponentInstance(ITimerFacility.class); startPeerManager(); state = StackState.STARTED; } finally { lock.unlock(); } } @Override @SuppressWarnings("unchecked") public void start(final Mode mode, long timeOut, TimeUnit timeUnit) throws IllegalDiameterStateException, InternalException { lock.lock(); try { if (state != StackState.STOPPED && state != StackState.CONFIGURED) { throw new IllegalDiameterStateException(); } scheduledFacility = concurrentFactory.getScheduledExecutorService(ProcessingMessageTimer.name()); assembler.getComponentInstance(IStatisticProcessor.class).start(); assembler.getComponentInstance(ISessionDatasource.class).start(); assembler.getComponentInstance(ITimerFacility.class); List<Peer> peerTable = peerManager.getPeerTable(); // considering only "to connect" peers are on the table at this time... final CountDownLatch barrier = new CountDownLatch(Mode.ANY_PEER.equals(mode) ? Math.min(peerTable.size(), 1) : peerTable.size()); StateChangeListener listener = new AbstractStateChangeListener() { @Override public void stateChanged(Enum oldState, Enum newState) { if (PeerState.OKAY.equals(newState)) { barrier.countDown(); } } }; for (Peer p : peerTable) { ((IPeer) p).addStateChangeListener(listener); } startPeerManager(); try { barrier.await(timeOut, timeUnit); if (barrier.getCount() != 0) { throw new InternalException("TimeOut"); } state = StackState.STARTED; } catch (InterruptedException e) { throw new InternalException("TimeOut"); } finally { for (Peer p : peerTable) { ((IPeer) p).remStateChangeListener(listener); } } } finally { lock.unlock(); } } private void startPeerManager() throws InternalException { try { if (peerManager != null) { peerManager.start(); } getMetaData().unwrap(IMetaData.class).updateLocalHostStateId(); } catch (Exception e) { throw new InternalException(e); } } @Override @SuppressWarnings("unchecked") public void stop(long timeOut, TimeUnit timeUnit, int disconnectCause) throws IllegalDiameterStateException, InternalException { lock.lock(); try { if (state == StackState.STARTED || state == StackState.CONFIGURED) { if (log.isInfoEnabled()) { log.info("(-)(-)(-)(-)(-) Stopping " + VersionProperties.instance.getProperty("vendor") + " DIAMETER Stack v" + VersionProperties.instance.getProperty("version") + " (-)(-)(-)(-)(-)"); } List<Peer> peerTable = peerManager.getPeerTable(); final CountDownLatch barrier = new CountDownLatch(peerTable.size()); StateChangeListener listener = new AbstractStateChangeListener() { @Override public void stateChanged(Enum oldState, Enum newState) { if (PeerState.DOWN.equals(newState)) { barrier.countDown(); } } }; for (Peer p : peerTable) { if (p.getState(PeerState.class).equals(PeerState.DOWN)) { barrier.countDown(); } else { ((IPeer) p).addStateChangeListener(listener); } } if (peerManager != null) { try { peerManager.stopping(disconnectCause); } catch (Exception e) { log.warn("Stopping error", e); } } try { barrier.await(timeOut, timeUnit); if (barrier.getCount() != 0) { throw new InternalException("TimeOut"); } } catch (InterruptedException e) { throw new InternalException("TimeOut"); } finally { state = StackState.STOPPED; for (Peer p : peerTable) { ((IPeer) p).remStateChangeListener(listener); } } assembler.getComponentInstance(ISessionDatasource.class).stop(); assembler.getComponentInstance(IStatisticProcessor.class).stop(); try { if (peerManager != null) { peerManager.stopped(); } // Clear all timeout tasks if (scheduledFacility != null) { concurrentFactory.shutdownNow(scheduledFacility); } } catch (Exception e) { log.warn("Stopped error", e); } state = StackState.STOPPED; if (log.isInfoEnabled()) { log.info("(-)(-)(-)(-)(-) Stopped " + VersionProperties.instance.getProperty("vendor") + " DIAMETER Stack v" + VersionProperties.instance.getProperty("version") + " (-)(-)(-)(-)(-)"); } } } finally { lock.unlock(); } } @Override public void destroy() { // Be friendly if (state == StackState.STARTED) { log.warn("Calling destroy() with Stack in STARTED state. Calling stop(REBOOTING) before, please do it yourself with the proper cause."); stop(DisconnectCause.REBOOTING); } lock.lock(); try { if (peerManager != null) { peerManager.destroy(); } if (assembler != null) { assembler.destroy(); } if (scheduledFacility != null) { concurrentFactory.shutdownNow(scheduledFacility); } } catch (Exception e) { log.warn("Destroy error", e); } finally { state = StackState.IDLE; lock.unlock(); } } @Override public boolean isActive() { return state == StackState.STARTED; } @Override public java.util.logging.Logger getLogger() { return java.util.logging.Logger.getAnonymousLogger(); } @Override public MetaData getMetaData() { if (state == StackState.IDLE) { throw new IllegalStateException("Meta data not defined"); } return assembler.getComponentInstance(IMetaData.class); } @Override @SuppressWarnings("unchecked") public <T extends BaseSession> T getSession(String sessionId, Class<T> clazz) throws InternalException { if (getState() == StackState.IDLE) { throw new InternalException("Illegal state of stack"); } BaseSession bs = assembler.getComponentInstance(ISessionDatasource.class).getSession(sessionId); return bs != null ? (T) bs : null; } @Override public boolean isWrapperFor(Class<?> aClass) throws InternalException { boolean isWrap = aClass == PeerTable.class; if (!isWrap) { isWrap = assembler.getChilds()[StackLayer.id()].getComponentInstance(aClass) != null; } if (!isWrap) { isWrap = assembler.getChilds()[ControllerLayer.id()].getComponentInstance(aClass) != null; } if (!isWrap) { isWrap = assembler.getChilds()[TransportLayer.id()].getComponentInstance(aClass) != null; } return isWrap; } @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> aClass) throws InternalException { Object unwrapObject = null; if (aClass == PeerTable.class) { unwrapObject = assembler.getComponentInstance(aClass); } // TODO: "layers" should be removed.... if (unwrapObject == null) { unwrapObject = assembler.getChilds()[StackLayer.id()].getComponentInstance(aClass); } if (unwrapObject == null) { unwrapObject = assembler.getChilds()[ControllerLayer.id()].getComponentInstance(aClass); } if (unwrapObject == null) { unwrapObject = assembler.getChilds()[TransportLayer.id()].getComponentInstance(aClass); } return (T) unwrapObject; } // Extended methods @Override public StackState getState() { return state; } @Override public Configuration getConfiguration() { return config; } @Override public IAssembler getAssemblerFacility() { return assembler; } @Override public void sendMessage(IMessage message) throws RouteException, AvpDataException, IllegalDiameterStateException, IOException { peerManager.sendMessage(message); } @Override public void addSessionListener(String sessionId, NetworkReqListener listener) { peerManager.addSessionReqListener(sessionId, listener); } @Override public void removeSessionListener(String sessionId) { peerManager.removeSessionListener(sessionId); } @Override public ScheduledExecutorService getScheduledFacility() { return scheduledFacility; } @Override public IConcurrentFactory getConcurrentFactory() { return this.concurrentFactory; } @Override public String configuration() { return config != null ? config.toString() : "not set"; } @Override public String metaData() { try { return getMetaData().toString(); } catch (Exception exc) { return "not set"; } } @Override public String peerDescription(String name) { try { for (Peer p : unwrap(PeerTable.class).getPeerTable()) { if (p.getUri().getFQDN().equals(name)) { return p.toString(); } } } catch (InternalException e) { log.debug("InternalException", e); } return "not set"; } @Override public String peerList() { try { return unwrap(PeerTable.class).getPeerTable().toString(); } catch (InternalException e) { return "not set"; } } @Override public void stop(int disconnectCause) { try { stop(10, TimeUnit.SECONDS, disconnectCause); } catch (Exception e) { log.debug("Exception", e); } } }