VirtualHostDispatcher.java

/***************************************************************************
   Copyright 2008 Emily Estes

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
***************************************************************************/
package net.metanotion.web.concrete;


import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;

import net.metanotion.util.Dispatcher;
import net.metanotion.util.Message;
import net.metanotion.util.PrefixConcurrentDictionary;

import net.metanotion.web.RequestObject;

/** This dispatcher delegates to a collection of sub dispatchers based on the host header of
HTTP request. It allows wildcard domains to be used, but matches on the most specific domain name in it's list.
(e.g. a.b.c.d would match over *.b.c.d, and *.b.c.d would match over *.c.d, etc.). This is quite similar to the
{@link net.metanotion.web.concrete.URIPrefixDispatcher} in design.
*/
public final class VirtualHostDispatcher implements Dispatcher<Object,RequestObject> {
	private static final String WILDCARD_PREFIX = "*.";
	/** The list of exact domains to match. This map is searched first for exact matches. */
	private final Map<String,Dispatcher> dnsToGroups = new ConcurrentHashMap<>();
	/** The wildcard domains to match. This will match the domain to the most "specific" domain it can. */
	private final PrefixConcurrentDictionary<Dispatcher> dnsToWildcardGroups = new PrefixConcurrentDictionary<>();

	/** Reverse the domain name, as we're going to match on "prefixes".
		@param name The domain name to reverse.
		@return the reversed domain name.
	*/
	public static String reverseDomainName(final String name) {
		final String[] chunks = name.split("\\.");
		final StringBuffer b = new StringBuffer("");
		String p = "";
		for(int i=chunks.length - 1; i >= 0; i--) {
			b.append(p);
			b.append(chunks[i]);
			p = ".";
		}
		return b.toString();
	}

	/** Search for the correct dispatcher to delegate to based on the domain name.
		@param d The domain name to search for.
		@return The dispatcher instance for the most specific host matched.
		@throws NoSuchElementException if no host group matches the domain.
	*/
	private Dispatcher findGroup(final String d) {
		final String domain = reverseDomainName(d);
		Dispatcher grp = dnsToGroups.get(domain);
		if(grp != null) { return grp; }
		// Search wildcard domains.
		grp = dnsToWildcardGroups.get(domain);
		if(grp != null) { return grp; }
		throw new NoSuchElementException("Bad domain name: " + domain);
	}

	/** Add a domain to a dispatcher. Wildcard domains are supported as the "lowest" part of the domain via the '*' operator.
		@param domain The domain to add to the list.
		@param dispatcher The dispatcher to use for requests that match that the domain.
		@return a (possibly new) VirtualHostDispatcher instance to use with the domain added.
	*/
	public VirtualHostDispatcher addDomain(final String domain, final Dispatcher dispatcher) {
		if(domain.startsWith(WILDCARD_PREFIX)) {
			dnsToWildcardGroups.put(reverseDomainName(domain.substring(2)), dispatcher);
		} else {
			dnsToGroups.put(reverseDomainName(domain), dispatcher);
		}
		return this;
	}

	/** Remove a domain from the patterns to match on.
		@param domain The domain to remove. This must match EXACTLY the domain specified in the call to .addDomain(...).
	*/
	public void removeDomain(final String domain) {
		if(domain.startsWith(WILDCARD_PREFIX)) {
			dnsToWildcardGroups.remove(reverseDomainName(domain.substring(2)));
		} else {
			dnsToGroups.remove(reverseDomainName(domain));
		}
	}

	// Dispatcher
	@Override public Message<Object> dispatch(final RequestObject request) {
		return findGroup(request.getServer()).dispatch(request);
	}
}