/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.jboss.services.binding.impl;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.jboss.logging.Logger;
import org.jboss.services.binding.DuplicateServiceException;
import org.jboss.services.binding.NoSuchBindingException;
import org.jboss.services.binding.ServiceBinding;
import org.jboss.services.binding.ServiceBindingMetadata;
import org.jboss.services.binding.ServiceBindingStore;
/**
* A Pojo implementation of {@link ServiceBindingStore}.
*
* @author Brian Stansberry
* @version $Revision: 88905 $
*/
public class PojoServiceBindingStore
implements ServiceBindingStore
{
private static final Logger log = Logger.getLogger(PojoServiceBindingStore.class);
/** Dummy value to make ConcurrentHashMap act like a Set */
private static final Object VALUE = new Object();
// ------------------------------------------------------------ Constructors
/** All bindings */
private final ConcurrentMap<ServiceBindingKey, ServiceBinding> bindings =
new ConcurrentHashMap<ServiceBindingKey, ServiceBinding>(16, (float) .75, 2);
/** Injected binding sets*/
private final ConcurrentMap<String, ServiceBindingSet> bindingSets =
new ConcurrentHashMap<String, ServiceBindingSet>(16, (float) .75, 2);
/** Injected base bindings whose ports are incremented for each set */
private final Map<ServiceBindingMetadata, Object> standardBindings =
new ConcurrentHashMap<ServiceBindingMetadata, Object>(16, (float) .75, 2);
private boolean started;
// ------------------------------------------------------------ Constructors
/**
* Creates a new PojoServiceBindingStore
*/
public PojoServiceBindingStore() {}
public PojoServiceBindingStore(Set<ServiceBindingSet> bindingSets, Set<ServiceBindingMetadata> standardBindings)
{
setServiceBindingSetsInternal(bindingSets);
setStandardBindingsInternal(standardBindings);
}
// ----------------------------------------------------- ServiceBindingStore
public synchronized void addServiceBinding(String serverName, ServiceBindingMetadata metadata)
throws DuplicateServiceException, UnknownHostException
{
addServiceBindingInternal(serverName, metadata, true);
log.debug("added binding " + metadata.getFullyQualifiedName() + " to " + serverName);
}
public synchronized ServiceBinding getServiceBinding(String serverName, String serviceName, String bindingName)
throws NoSuchBindingException
{
ServiceBinding binding = bindings.get(new ServiceBindingKey(serverName, serviceName, bindingName));
if (binding == null)
{
throw new NoSuchBindingException(serverName, serviceName, bindingName);
}
return binding;
}
public synchronized Set<ServiceBinding> getServiceBindings(String serverName)
{
validateServerName(serverName);
Set<ServiceBinding> result = new HashSet<ServiceBinding>();
for (Map.Entry<ServiceBindingKey, ServiceBinding> entry : bindings.entrySet())
{
if (serverName.equals(entry.getKey().serverName))
{
result.add(entry.getValue());
}
}
return result;
}
public synchronized void removeServiceBinding(String serverName, ServiceBindingMetadata metadata)
{
validateServerName(serverName);
bindings.remove(new ServiceBindingKey(serverName, metadata));
// For management purposes, treat this as an override
ServiceBindingSet bindingSet = bindingSets.get(serverName);
bindingSet.getOverrideBindings().remove(metadata);
}
public synchronized String getDefaultHostName(String serverName)
{
validateServerName(serverName);
return bindingSets.get(serverName).getDefaultHostName();
}
public synchronized int getDefaultPortOffset(String serverName)
{
validateServerName(serverName);
return bindingSets.get(serverName).getPortOffset();
}
// ------------------------------------------------------------------ Public
/**
* Sets the base set of bindings that should be associated with each binding set,
* adjusted to conform to the binding set's defaultHostName and offset.
*
* @param bindings the set of base bindings. May be <code>null</code>
* @throws DuplicateServiceException
* @throws UnknownHostException
*
* @throws IllegalStateException if invoked after {@link #start()}
*/
public synchronized void setStandardBindings(Set<ServiceBindingMetadata> bindings) throws UnknownHostException, DuplicateServiceException
{
setStandardBindingsInternal(bindings);
if (started)
{
log.debug("updated standard bindings injected");
if (log.isTraceEnabled())
{
for (ServiceBindingMetadata sbm : bindings)
{
log.trace(sbm.getFullyQualifiedName() + " port is " + sbm.getPort() + " " + sbm.isFixedHostName() + "/" + sbm.isFixedPort());
}
}
establishBindings();
}
}
public synchronized void setServiceBindingSets(Set<ServiceBindingSet> sets) throws UnknownHostException, DuplicateServiceException
{
setServiceBindingSetsInternal(sets);
if (started)
{
log.debug("updated ServiceBindingSets injected");
if (log.isTraceEnabled())
{
for (ServiceBindingSet set : sets)
{
log.trace(set.getName() + " offset is " + set.getPortOffset() + " defaultHostName is " + set.getDefaultHostName());
java.util.Set<ServiceBindingMetadata> ovr = set.getOverrideBindings();
for (ServiceBindingMetadata sbm : ovr)
{
log.trace(sbm.getFullyQualifiedName() + " port is " + sbm.getPort() + " " + sbm.isFixedHostName() + "/" + sbm.isFixedPort());
}
}
}
establishBindings();
}
}
/**
* Builds the runtime sets of bindings from the injected base bindings
* and ServiceBindingSets.
*
* @throws DuplicateServiceException
* @throws UnknownHostException
*/
public void start() throws DuplicateServiceException, UnknownHostException
{
establishBindings();
this.started = true;
}
public void stop()
{
this.bindings.clear();
this.started = false;
}
// -------------------------------------------------------------- Management
/**
* Gets the base set of bindings that should be associated with each binding set,
* but with that binding set's {@link ServiceBindingSet#getPortOffset() port offset}
* applied to the port value.
*
* @return the set of base bindings
*/
public Set<ServiceBindingMetadata> getStandardBindings()
{
return new HashSet<ServiceBindingMetadata>(standardBindings.keySet());
}
/**
* See {@link #getServiceBindingSets()}
*
* @return the binding sets
*
* @deprecated use {@link #getServiceBindingSets()}
*/
public Set<ServiceBindingSet> getBindingSets()
{
return getServiceBindingSets();
}
/**
* Gets the {@link ServiceBindingSet}s associated with this store.
*
* @return the binding sets. Will not return <code>null</code>
*/
public Set<ServiceBindingSet> getServiceBindingSets()
{
return new HashSet<ServiceBindingSet>(bindingSets.values());
}
/**
* Add a ServiceBinding to all binding sets in the store. For each binding
* set, a new ServiceBinding is added whose serviceName and bindingName
* properties match the passed binding. If <the given <code>binding</code>'s
* <code>fixeHostName</code> property is <code>false</code>, the new binding's
* hostName matches the target set's {@link #getDefaultHostName(String) default host name}.
* If <code>binding</code>'s <code>fixedPort</code> property is <code>false</code>,
* the new binding's port is derived by taking the port from the passed binding
* and incrementing it by the target set's
* {@link #getDefaultPortOffset(String) default port offset}.
*
* @param metadata metadata about the binding to add
*
* @throws DuplicateServiceException thrown if a configuration for the
* <serverName, serviceName> pair already exists.
*/
public synchronized void addServiceBinding(ServiceBindingMetadata metadata) throws DuplicateServiceException
{
// Add to the runtime objects
for (ServiceBindingSet bindingSet : bindingSets.values())
{
try
{
addServiceBindingInternal(bindingSet.getName(), metadata, false);
}
catch (UnknownHostException e)
{
String hostName = metadata.isFixedHostName() ? metadata.getHostName() : bindingSet.getDefaultHostName();
throw new IllegalStateException("Cannot convert " + hostName + " into an InetAddress");
}
}
// Add to the managed object map
standardBindings.put(metadata, VALUE);
}
/**
* Creates a new {@link ServiceBindingMetadata} from the given params
* and calls {@link #addServiceBinding(ServiceBindingMetadata)}.
*
* @param serviceName the name of the service. Cannot be <code>null</code>
* @param bindingName name qualifier for the binding within the service.
* May be <code>null</code>
* @param hostName hostname or IP address to which the binding should be
* bound. Use <code>null</code> to indicate the host name
* should be the default host name for each binding set
* @param serviceConfig the configuration to add
* @param fixed <code>true</code> if the binding's port should remain fixed
* when added to each binding set; <code>false</code> if it
* should be offset by the binding set's port offset
*
* @throws DuplicateServiceException thrown if a configuration for the
* <serverName, serviceName> pair already exists.
*/
public synchronized void addServiceBinding(String serviceName, String bindingName, String hostName, int port, boolean fixedPort)
throws DuplicateServiceException, UnknownHostException
{
addServiceBinding(serviceName, bindingName, null, hostName, port, false, fixedPort);
}
/**
* Creates a new {@link ServiceBindingMetadata} from the given params
* and calls {@link #addServiceBinding(ServiceBindingMetadata)}.
*
* @param serviceName the name of the service. Cannot be <code>null</code>
* @param bindingName name qualifier for the binding within the service.
* May be <code>null</code>
* @param description helpful description of the binding; may be <code>null</code>
* @param hostName hostname or IP address to which the binding should be
* bound. Often <code>null</code> since the host name typically
* comes from the default host name for each binding set
* @param port port the binding should use
* @param fixedHostName <code>true</code> if the binding's <code>hostName</code>
* should remain fixed when added to each binding set;
* <code>false</code> if it should be changed to the binding set's
* default host name
* @param fixedPort <code>true</code> if the binding's port should remain fixed
* when added to each binding set; <code>false</code> if it
* should be offset by the binding set's port offset
*
* @throws DuplicateServiceException thrown if a configuration for the
* <serverName, serviceName> pair already exists.
*/
public synchronized void addServiceBinding(String serviceName, String bindingName, String description, String hostName, int port, boolean fixedHostName, boolean fixedPort)
throws DuplicateServiceException, UnknownHostException
{
ServiceBindingMetadata metadata = new ServiceBindingMetadata();
metadata.setServiceName(serviceName);
metadata.setBindingName(bindingName);
metadata.setHostName(hostName);
metadata.setPort(port);
metadata.setFixedPort(fixedPort);
addServiceBinding(metadata);
}
/**
* Remove a service configuration from all binding sets in the store.
*
* @param metadata the binding
*/
public synchronized void removeServiceBinding(ServiceBindingMetadata metadata)
{
// Remove from runtime sets
for (String serverName : bindingSets.keySet())
{
removeServiceBinding(serverName, metadata);
}
// Remove from managed set
standardBindings.remove(metadata);
}
/**
* Remove a service configuration from all binding sets in the store.
*
* @param serviceName the name of the service. Cannot be <code>null</code>
* @param bindingName name qualifier for the binding within the service.
* May be <code>null</code>
*/
public synchronized void removeServiceBinding(String serviceName, String bindingName)
{
ServiceBindingMetadata metadata = new ServiceBindingMetadata(serviceName, bindingName);
removeServiceBinding(metadata);
}
// ------------------------------------------------------------------ Private
private static boolean safeEquals(Object a, Object b)
{
return (a == b || (a != null && a.equals(b)));
}
private void validateServerName(String serverName)
{
if (bindingSets.containsKey(serverName) == false)
throw new IllegalArgumentException("unknown serverName " +serverName);
}
private void setServiceBindingSetsInternal(Set<ServiceBindingSet> sets)
{
this.bindingSets.clear();
if (sets != null)
{
for (ServiceBindingSet bindingSet : sets)
{
this.bindingSets.put(bindingSet.getName(), bindingSet);
}
}
}
private void setStandardBindingsInternal(Set<ServiceBindingMetadata> bindings)
{
standardBindings.clear();
if (bindings != null)
{
for (ServiceBindingMetadata binding : bindings)
{
standardBindings.put(binding, VALUE);
}
}
}
private void establishBindings() throws UnknownHostException, DuplicateServiceException
{
synchronized (this)
{
this.bindings.clear();
// Establish the override bindings first, so when we add the
// fixed and portOffset, we get DuplicateServiceException
for (ServiceBindingSet bindingSet : bindingSets.values())
{
for (ServiceBindingMetadata binding : bindingSet.getOverrideBindings())
{
addServiceBindingInternal(bindingSet.getName(), binding, false);
}
}
// Establish the standard bindings
for (ServiceBindingMetadata metadata : standardBindings.keySet())
{
for (ServiceBindingSet bindingSet : bindingSets.values())
{
try
{
addServiceBindingInternal(bindingSet.getName(), metadata, false);
}
catch (DuplicateServiceException e)
{
if (bindingSet.getOverrideBindings().contains(metadata) == false)
{
throw e;
}
}
}
}
}
}
private void addServiceBindingInternal(String serverName, ServiceBindingMetadata metadata, boolean addToBindingSet)
throws DuplicateServiceException, UnknownHostException
{
validateServerName(serverName);
ServiceBindingSet bindingSet = bindingSets.get(serverName);
ServiceBinding binding = new ServiceBinding(metadata, bindingSet.getDefaultHostName(), bindingSet.getPortOffset());
ServiceBinding oldBinding = bindings.putIfAbsent(new ServiceBindingKey(serverName, metadata), binding);
if (oldBinding != null &&
(safeEquals(oldBinding.getHostName(), binding.getHostName()) == false
|| oldBinding.getPort() != binding.getPort()))
{
throw new DuplicateServiceException(serverName, binding);
}
if (addToBindingSet)
{
// For management purposes, treat this as an override
bindingSet.getOverrideBindings().add(metadata);
}
}
private static class ServiceBindingKey
{
private final String serverName;
private final String serviceName;
private final String bindingName;
private ServiceBindingKey(String serverName, ServiceBindingMetadata binding)
{
if (serverName == null)
{
throw new IllegalArgumentException("serverName is null");
}
if (binding == null)
{
throw new IllegalArgumentException("binding is null");
}
if (binding.getServiceName() == null)
{
throw new IllegalStateException("binding's serviceName is null");
}
this.serverName = serverName;
this.serviceName = binding.getServiceName();
this.bindingName = binding.getBindingName();
}
private ServiceBindingKey(String serverName, String serviceName, String bindingName)
{
if (serverName == null)
{
throw new IllegalArgumentException("serverName is null");
}
if (serviceName == null)
{
throw new IllegalArgumentException("serviceName is null");
}
this.serverName = serverName;
this.serviceName = ServiceBindingMetadata.canonicalizeServiceName(serviceName);
this.bindingName = bindingName;
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof ServiceBindingKey)
{
ServiceBindingKey other = (ServiceBindingKey) obj;
return (this.serverName.equals(other.serverName)
&& this.serviceName.equals(other.serviceName)
&& safeEquals(this.bindingName, other.bindingName));
}
return false;
}
@Override
public int hashCode()
{
int result = 17;
result += 23 * this.serverName.hashCode();
result += 23 * this.serviceName.hashCode();
result += 23 * (this.bindingName == null ? 0 : this.bindingName.hashCode());
return result;
}
}
}