All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.coos.messaging.routing.DefaultRouter Maven / Gradle / Ivy

/**
 * COOS - Connected Objects Operating System (www.connectedobjects.org).
 *
 * Copyright (C) 2009 Telenor ASA and Tellu AS. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This library 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 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 * You may also contact one of the following for additional information:
 * Telenor ASA, Snaroyveien 30, N-1331 Fornebu, Norway (www.telenor.no)
 * Tellu AS, Hagalokkveien 13, N-1383 Asker, Norway (www.tellu.no)
 */
package org.coos.messaging.routing;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.coos.messaging.ConnectingException;
import org.coos.messaging.Link;
import org.coos.messaging.Message;
import org.coos.messaging.Processor;
import org.coos.messaging.ProcessorException;
import org.coos.messaging.ProcessorInterruptException;
import org.coos.messaging.impl.DefaultProcessor;
import org.coos.messaging.processor.LocalCoosAddressConverter;
import org.coos.messaging.util.Log;
import org.coos.messaging.util.LogFactory;
import org.coos.messaging.util.URIHelper;
import org.coos.messaging.util.UuidHelper;

/**
 * The DefaultRouter (actually default point to point router) contains the point to point routing mechanisms
 * 
 * @author Knut Eilif Husa, Tellu AS
 */
public class DefaultRouter extends DefaultProcessor implements Router {

	private Collection preProcessors = new ConcurrentLinkedQueue();
	private Collection postProcessors = new ConcurrentLinkedQueue();

	private Map routingTables = new ConcurrentHashMap();
	private Map aliasTable = new ConcurrentHashMap();
	// todo let the links be handled by the coos instance
	private Map links = new ConcurrentHashMap();
	private Map routingAlgorithms = new ConcurrentHashMap();
	// The uuid of the router in the different segments it participates in. If
	// more than one uuid, this router is a gateway router
	private Collection routerUuids = new ConcurrentLinkedQueue();
	private Collection QoSClasses = new ConcurrentLinkedQueue();
	private String defaultQoSClass;
	private String COOSInstanceName;
	private boolean running = false;
	private boolean enabled = true;
	private boolean loggingEnabled = false;
	private Link defaultGw = null;

	private final Log logger = LogFactory.getLog(this.getClass(), false);

	// This constructor is only used by tests
	public DefaultRouter(String routerUuid) {
		COOSInstanceName = routerUuid;
		new LinkStateAlgorithm(this, routerUuid);
		addQoSClass(Link.DEFAULT_QOS_CLASS, true);
	}

	public DefaultRouter() {
		addQoSClass(Link.DEFAULT_QOS_CLASS, true);
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public void removeRouterUuid(String routerUuid) {
		routerUuids.remove(routerUuid);
	}

	public void setLoggingEnabled(boolean loggingEnabled) {
		this.loggingEnabled = loggingEnabled;
		for (RoutingAlgorithm routingAlgorithm : routingAlgorithms.values()) {
			routingAlgorithm.setLoggingEnabled(loggingEnabled);
		}
	}

	public void processMessage(Message msg) {

		// Router preprocessors
		for (RouterProcessor routerProcessor : preProcessors) {
			try {
				routerProcessor.processMessage(msg);
			} catch (ProcessorInterruptException e) {
				return;
			} catch (ProcessorException e) {
				e.printStackTrace();
			}
		}

		if (msg.getReceiverEndpointUri() == null) {
			logger.warn("Message from " + msg.getSenderEndpointUri() + " missing receiver address");
			replyErrorReason(msg, Message.ERROR_NO_RECEIVER);
			return;
		}

		String qosClass = msg.getHeader(Message.QOS_CLASS);
		if (qosClass == null) {
			qosClass = defaultQoSClass;
		}

		Map routingTable = routingTables.get(qosClass);

		String hops = msg.getHeader(Message.HOPS);
		if (hops == null) {
			hops = "1";
		} else {
			hops = String.valueOf(Integer.parseInt(hops) + 1);
		}
		msg.setHeader(Message.HOPS, hops);
		if (Integer.parseInt(hops) > 244) {
			logger.warn("Message from " + msg.getSenderEndpointUri() + ", to: " + msg.getReceiverEndpointUri() + " too many hops");
			replyErrorReason(msg, Message.ERROR_TOO_MANY_HOPS);
			return;
		}

		String uuid = resolveAlias(msg);

		if (uuid == null) {
			// will never occur
			replyErrorReason(msg, Message.ERROR_NO_ALIAS + ":" + msg.getReceiverEndpointUri());
			return;
		}

		Link link;

		// check if the message is destined towards this router
		if (routerUuids.contains(uuid)) {
			logger.putMDC(UUID_PREFIX, uuid);
			if (msg.getType().equals(Message.TYPE_ROUTING_INFO)) {
				routingAlgorithms.get(UuidHelper.getSegment(uuid)).processRoutingInfo(msg);
			} else if (msg.getType().equals(Message.TYPE_ALIAS)) {
				try {
					setLinkAliases((Vector) msg.getBody(), msg.getMessageContext().getInBoundChannel().getOutLink());
				} catch (ProcessorException e) {
					e.printStackTrace();
				}
			}
		} else {

			// The routing step
			link = route(uuid, msg, routingTable);
			
			if(link != null){
				msg.getMessageContext().setNextLink(link);
			}

			// Router postprocessors
			for (RouterProcessor routerProcessor : postProcessors) {
				try {
					routerProcessor.processMessage(msg);
				} catch (ProcessorInterruptException e) {
					return;
				} catch (ProcessorException e) {
					e.printStackTrace();
				}
			}

			// The sending step
			if (link != null) {

				if (msg.getHeader(Message.TRACE_ROUTE) != null) {
					String trace = msg.getHeader(Message.TRACE);
					if (trace == null) {
						trace = COOSInstanceName;
					}
					msg.setHeader(Message.TRACE, trace + " -> " + link.getDestinationUuid());
				}

				try {
					link.processMessage(msg);
				} catch (ProcessorException e) {
					replyErrorReason(msg, e.getMessage());
				}
			} else {
				String errorMsg;
				URIHelper helper = new URIHelper(msg.getReceiverEndpointUri());
				String alias = helper.getEndpoint();
				if(uuid.equals("null")){
					errorMsg = COOSInstanceName + ": No uuid for alias " + alias + ", No route from " + COOSInstanceName;
				} else {
					if(uuid.equals(alias)){
						errorMsg = COOSInstanceName + ": No route to: " + alias + " from " + COOSInstanceName;
					} else {
						errorMsg = COOSInstanceName + ": No route to: " + alias +" / " + uuid + " from " + COOSInstanceName;
					}
				}
				logger.warn(errorMsg);
				replyErrorReason(msg, Message.ERROR_NO_ROUTE);
			}
		}
	}

	/**
	 * This method does dynamic registers and unregisters aliases to link
	 * 
	 * @param regAliases
	 *            - An vector containing all aliases to be registered
	 * @param outlink
	 *            - The link to register the aliases with. This link is always directed towards an endpoint
	 * @throws ProcessorException
	 */
	public void setLinkAliases(Vector regAliases, Link outlink) throws ProcessorException {
		String segment = UuidHelper.getSegment(outlink.getDestinationUuid());
		if (!segment.equals(".")) {
			segment += ".";
		}
		// Qualify aliases
		for (int i = 0; i < regAliases.size(); i++) {
			String alias = (String) regAliases.get(i);
			if (!alias.startsWith(Router.DICO_SEGMENT + ".") && !alias.startsWith(segment)) {
				// unqualified aliases are qualified by prefixing with segment
				// name
				String qualifiedAlias = segment + alias;
				;
				regAliases.remove(alias);
				regAliases.add(qualifiedAlias);
			}
		}

		Vector curAliases = outlink.getAlises();
		Iterator itCurAliases = curAliases.iterator();

		// remove aliases that are not present anymore
		while (itCurAliases.hasNext()) {
			String alias = (String) itCurAliases.next();
			if (!regAliases.contains(alias)) {
				itCurAliases.remove();
				removeAlias(alias);
			}
		}

		// Add all aliases
		Iterator itRegAliases = regAliases.iterator();
		while (itRegAliases.hasNext()) {
			String alias = (String) itRegAliases.next();
			outlink.addAlias(alias);
			String oldToUuid = aliasTable.get(alias);
			if (oldToUuid != null && !oldToUuid.equals(outlink.getDestinationUuid())) {
				Iterator itRegAliases2 = regAliases.iterator();
				while (itRegAliases2.hasNext()) {
					removeAlias((String) itRegAliases2.next());
				}

				throw new ProcessorException("Can not register alias:" + alias + " since this alias is occupied for endpoint with uuid "
						+ outlink.getDestinationUuid());
			}
			putAlias(alias, outlink.getDestinationUuid());
		}
	}

	/**
	 * This method resolves receiver URIs into uuids. It handles both URIs containing aliases and uuids
	 * 
	 * @param msg
	 *            the message to resolve alias for
	 * @return the uuid
	 */
	public String resolveAlias(Message msg) {
		URIHelper helper = new URIHelper(msg.getReceiverEndpointUri());
		String uuid;
		String alias;
		alias = helper.getEndpoint();
		String segment;

		if (alias != null && !helper.isEndpointUuid()) {

			// routing on alias
			// To account for that not all messages arrives the router via an
			// incoming link.
			// I.e. the routingInfo from this router
			if (msg.getMessageContext().getInBoundLink() != null && msg.getMessageContext().getInBoundLink().getDestinationUuid() != null) {
				segment = UuidHelper.getSegment(msg.getMessageContext().getInBoundLink().getDestinationUuid());
			} else {
				segment = ".";
			}

			if (!segment.equals(".")) {
				segment += ".";
			}
			if (!alias.startsWith(segment) && !alias.startsWith(Router.DICO_SEGMENT + ".")) {
				// the alias is unqualified, first try to route in segment
				// namespace
				String qualifiedAlias = segment + alias;

				uuid = aliasTable.get(qualifiedAlias);
				if (uuid == null) {
					// If not found in segment namespace, then try to route in
					// dico namespace
					qualifiedAlias = DICO_SEGMENT + "." + alias;
					uuid = aliasTable.get(qualifiedAlias);
				}
			} else {
				// If fully qualified alias then lookup in aliasTable
				uuid = aliasTable.get(alias);
			}
			if (uuid == null) {
				// we always return with a string in order to allow for
				// defaultgw
				uuid = "null";
			}
		} else {
			uuid = alias;
		}
		return uuid;
	}

	/**
	 * This is the core of the routing algorithm
	 * 
	 */
	public Link route(String uuid, Message msg, Map routingTable) {
		
		URIHelper helper = new URIHelper(msg.getSenderEndpointUri());
		if (msg.getMessageContext().getInBoundChannel() != null) {
			// Only populate routingtable with senderSegment/senderUuid if the
			// message enters the router through an incoming channel
			String destUuid = msg.getMessageContext().getInBoundLink().getDestinationUuid();
			String curSegment = UuidHelper.getSegment(destUuid);

			String senderEndpointUuid = helper.getEndpoint();
			String senderSegment = UuidHelper.getSegment(senderEndpointUuid);
			if (senderSegment.equals(curSegment)) {
				// If senderEndpointUuid belongs to the same segment as the
				// incoming channel
				// populate routingtable with senderUuid
				if (!routingTable.containsKey(senderEndpointUuid)) {
					routingTable.put(senderEndpointUuid, msg.getMessageContext().getInBoundChannel().getOutLink());
				}
			} else {
				// If senderEndpointUuid not belongs to the same segment as the
				// incoming channel
				// populate routingtable with segment of the senderUuid
				if (!routingTable.containsKey(senderSegment)) {
					routingTable.put(senderSegment, msg.getMessageContext().getInBoundChannel().getOutLink());
				}
			}
		}

		Link link = routingTable.get(uuid);

		// route down
		if (link == null) {
			String toSegment = UuidHelper.getSegment(uuid);
			Link inboundLink = msg.getMessageContext().getInBoundLink();
			if (inboundLink == null) {
				return null; // The message has not arrived this router through
				// an inboundLink. It is invalid and can not be
				// routed
			}
			String destUuid = inboundLink.getDestinationUuid();
			if (destUuid == null) {
				logger.warn(COOSInstanceName + ":destinationUuid is null on incoming link : " + inboundLink.getLinkId());
			}
			String curSegment = UuidHelper.getSegment(destUuid);

			if (!toSegment.equals(curSegment)) {
				// route down
				while (!toSegment.equals(".")) {
					link = routingTable.get(toSegment);
					if (link != null) {
						break;
					} else {
						toSegment = UuidHelper.getParentSegment(toSegment);
					}
				}

				// route up
				if (link == null) {
					if (curSegment != null) {
						String parentSegment = UuidHelper.getParentSegment(curSegment);
						if (parentSegment != null) {
							link = routingTable.get(parentSegment);
						}
					}
				}
			}
		}

		// route along defaultGw if set. Must be different from the incoming Link, Must not be in segment localcoos
		if (link == null 
				&& defaultGw != null 
				&& defaultGw != msg.getMessageContext().getInBoundChannel().getOutLink() 
				&& !UuidHelper.getSegment(uuid).equals(Router.LOCAL_SEGMENT)) {
			link = defaultGw;
		}
		
		return link;
	}

	public void replyErrorReason(Message msg, String message) {
		// Only send error indication on type msg. i.e. from an endpoint
		if (msg.getHeader(Message.TYPE).equals(Message.TYPE_MSG)) {
			msg.setReceiverEndpointUri(msg.getSenderEndpointUri());
			msg.setHeader(Message.TYPE, Message.TYPE_ERROR);
			msg.setHeader(Message.ERROR_REASON, message);
			processMessage(msg);
		}
	}

	public Processor getDefaultProcessor() {
		return this;
	}

	/**
	 * Adding a link to the router. Can either be a link to an endpoint/router or a link to another segment
	 * 
	 * @param routerUuid
	 * @param link
	 * @throws ConnectingException
	 */
	public void addLink(String routerUuid, Link link) throws ConnectingException {
		link.setDestinationUuid(routerUuid);
		links.put(link.getLinkId(), link);

		if (link.getChannel() != null && link.getChannel().isDefaultGw()) {
			defaultGw = link;
		}

		if (UuidHelper.isUuid(routerUuid)) {
			// It is a router or endpoint
			String seg = UuidHelper.getSegment(routerUuid);
			RoutingAlgorithm algorithm = routingAlgorithms.get(seg);
			if (algorithm == null) {
				throw new ConnectingException("Router is not attached to segment: " + seg);
			}
			algorithm.publishLink(link);
		} else {
			// it is a segment
			routingAlgorithms.get(routerUuid).publishLink(link);
		}
		logger.debug(getCOOSInstanceName() + ": Adding link: "+ link);
	}

	public Link getLink(String destinationUuid) {
		if (destinationUuid != null) {
			for (Link link : links.values()) {
				if (link.getDestinationUuid().equals(destinationUuid)) {
					return link;
				}
			}
		}
		return null;
	}

	public void removeLinkById(String linkId) {
		Link link = links.get(linkId);
		if (link != null) {
			link.setCost(LinkCost.MAX_VALUE);
		}

	}

	public void removeLink(String destinationUuid) {
		for (Link link : links.values()) {
			if (link.getDestinationUuid().equals(destinationUuid)) {
				link.setCost(LinkCost.MAX_VALUE);
				routingAlgorithms.get(UuidHelper.getSegment(destinationUuid)).publishLink(link);
			}
		}
	}

	public void addQoSClass(String QoSClass, boolean isDefaultQoSClass) {
		this.QoSClasses.add(QoSClass);
		if (defaultQoSClass == null || isDefaultQoSClass) {
			defaultQoSClass = QoSClass;
		}

		// create routing tables
		if (routingTables.get(QoSClass) == null) {
			routingTables.put(QoSClass, new ConcurrentHashMap());
		}
	}

	public Collection getQoSClasses() {
		return this.QoSClasses;
	}

	public void addPreProcessor(RouterProcessor preProcessor) {
		preProcessor.setRouter(this);
		preProcessors.add(preProcessor);
	}

	public void addPostProcessor(RouterProcessor postProcessor) {
		postProcessor.setRouter(this);
		postProcessors.add(postProcessor);
	}

	public String getCOOSInstanceName() {
		return this.COOSInstanceName;
	}

	public void setCOOSInstanceName(String instanceName) {
		this.COOSInstanceName = instanceName;
	}

	public synchronized void addRoutingAlgorithm(String routerUuid, RoutingAlgorithm routingAlgorithm) {
		if (!UuidHelper.isRouterUuid(routerUuid)) {
			throw new IllegalArgumentException("Router uuid must start with prefix " + ROUTER_UUID_PREFIX);
		}

		Processor processor = null;
		if (UuidHelper.getSegment(routerUuid).equals(Router.LOCAL_SEGMENT)) {
			routerUuid = UuidHelper.replaceSegment(routerUuid, COOSInstanceName + Router.LOCAL_SEGMENT);
			processor = new LocalCoosAddressConverter(COOSInstanceName);
			this.routingAlgorithms.put(Router.LOCAL_SEGMENT, routingAlgorithm);
		}

		this.routingAlgorithms.put(UuidHelper.getSegment(routerUuid), routingAlgorithm);
		routerUuids.add(routerUuid);

		if (routerUuids.size() > 1) {
			for (String uuid : routerUuids) {
				if (!routerUuid.equals(uuid)) {
					Link link = new Link(0);
					link.addFilterProcessor(processor);
					link.setChainedProcessor(this);
					try {
						addLink(UuidHelper.getSegment(uuid), link);
					} catch (Exception e) {
						e.printStackTrace();
					}

					link = new Link(0);
					link.addFilterProcessor(processor);
					link.setChainedProcessor(this);
					try {
						addLink(UuidHelper.getSegment(routerUuid), link);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}

		if (running) {
			try {
				routingAlgorithm.start();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public void aliasesUpdated() {

		// remove global aliases
		for (Link link : links.values()) {
			Collection aliases = link.getAlises();
			for (Iterator iterator = aliases.iterator(); iterator.hasNext();) {
				String alias = (String) iterator.next();
				if (alias.startsWith(Router.DICO_SEGMENT + ".") && !aliasTable.containsKey(alias)) {
					iterator.remove();
				}
			}
		}

		// add global aliases in crossegment links
		for (String alias : aliasTable.keySet()) {
			if (alias.startsWith(Router.DICO_SEGMENT + ".")) {
				String aliasUuid = aliasTable.get(alias);
				String aliasSegment = UuidHelper.getSegment(aliasUuid);
				for (Link link : links.values()) {
					String segment = UuidHelper.getSegment(link.getDestinationUuid());
					if (aliasSegment.equals(segment) && UuidHelper.isSegment(link.getDestinationUuid())) {
						link.addAlias(alias);
					}
				}
			}
		}

	}

	public String getDefaultQoSClass() {
		return defaultQoSClass;
	}

	public RoutingAlgorithm getRoutingAlgorithm(String segment) {
		return routingAlgorithms.get(segment);
	}

	public Map getRoutingTables() {
		return routingTables;
	}

	public Map getRoutingTable(String qos) {
		return routingTables.get(qos);
	}

	public Map getLinks() {
		return links;
	}

	public Link getLinkById(String id) {

		return links.get(id);
	}

	public void start() throws Exception {
		if (!running && enabled) {
			for (RoutingAlgorithm routingAlgorithm : routingAlgorithms.values()) {
				routingAlgorithm.start();
			}

			running = true;
		}
	}

	public void stop() throws Exception {
		for (RoutingAlgorithm routingAlgorithm : routingAlgorithms.values()) {
			routingAlgorithm.stop();
		}

		running = false;
	}

	public String toString() {
		return "Router " + COOSInstanceName;
	}

	public Map getAliasTable() {
		return aliasTable;
	}

	public void putAlias(String alias, String toUuid) {
		String oldToUuid = aliasTable.get(alias);
		if (oldToUuid != null && !oldToUuid.equals(toUuid)) {
			logger.warn("Possible alias conflict for alias: " + alias + ". Was pointing to : " + oldToUuid + ". Now pointing to :" + toUuid + ".");
		}
		aliasTable.put(alias, toUuid);
		aliasesUpdated();
	}

	public void removeAlias(String alias) {
		aliasTable.remove(alias);
		aliasesUpdated();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy