/*
* JBoss, Home of Professional Open Source
* Copyright 2010, JBoss Inc., and individual contributors as indicated
* 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.jboss.ha.jndi.ispn;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.NotContextException;
import org.infinispan.Cache;
import org.infinispan.CacheException;
import org.infinispan.manager.CacheContainer;
import org.infinispan.tree.Fqn;
import org.infinispan.tree.Node;
import org.infinispan.tree.TreeCache;
import org.jboss.ha.ispn.CacheContainerRegistry;
import org.jboss.ha.ispn.tree.DefaultTreeCacheFactory;
import org.jboss.ha.ispn.tree.TreeCacheFactory;
import org.jboss.logging.Logger;
import org.jnp.interfaces.Naming;
import org.jnp.interfaces.NamingContext;
import org.jnp.interfaces.NamingParser;
/**
* This class utilizes Infinispan to provide a HA-JNDI implementation.
*
* @author Jerry Gauthier
* @author Brian Stansberry
* @author Scott Marlow
*
* @version $Revision: 108179 $
*/
public class DistributedTreeManager
implements Naming, org.jboss.ha.jndi.spi.DistributedTreeManager
{
private static final Logger LOG = Logger.getLogger(DistributedTreeManager.class);
private static final NamingParser PARSER = new NamingParser();
private static final Fqn FQN_ROOT = Fqn.root();
private final CacheContainerRegistry registry;
private final TreeCacheFactory treeCacheFactory;
private volatile String containerName;
private volatile String cacheName = "DistributedTreeManager";
private volatile Naming haStub;
private volatile TreeCache<String, Binding> cache;
public DistributedTreeManager(CacheContainerRegistry registry)
{
this(registry, new DefaultTreeCacheFactory());
}
public DistributedTreeManager(CacheContainerRegistry registry, TreeCacheFactory factory)
{
this.registry = registry;
this.treeCacheFactory = factory;
}
@Override
public void init()
{
CacheContainer container = this.registry.getCacheContainer(this.containerName);
Cache<String, Binding> cache = (this.cacheName == null) ? container.<String, Binding>getCache() : container.<String, Binding>getCache(this.cacheName);
this.cache = this.treeCacheFactory.createTreeCache(cache);
if (!cache.getStatus().allowInvocations())
{
this.cache.start();
}
}
@Override
public void shutdown()
{
this.cache.stop();
}
public void setCacheContainerName(String containerName)
{
this.containerName = containerName;
}
public void setCacheName(String cacheName)
{
this.cacheName = cacheName;
}
@Override
public Naming getHAStub()
{
return this.haStub;
}
@Override
public void setHAStub(Naming haStub)
{
this.haStub = haStub;
}
// Naming implementation -----------------------------------------
@Override
public void bind(Name name, Object obj, String className) throws NamingException
{
if (LOG.isTraceEnabled())
{
LOG.trace("bind, name="+name);
}
this.internalBind(name, obj, className, false);
}
@Override
public void rebind(Name name, Object obj, String className) throws NamingException
{
if (LOG.isTraceEnabled())
{
LOG.trace("rebind, name="+name);
}
this.internalBind(name, obj, className, true);
}
@Override
public void unbind(Name name) throws NamingException
{
if (LOG.isTraceEnabled())
{
LOG.trace("unbind, name="+name);
}
if (name.isEmpty())
{
// Empty names are not allowed
throw new InvalidNameException("Name is empty");
}
// is the name a context?
try
{
Fqn temp = Fqn.fromRelativeFqn(FQN_ROOT, Fqn.fromString(name.toString()));
// TODO why not jst call remove -- why hasChild first?
if (this.cache.getRoot().hasChild(temp))
{
this.cache.removeNode(temp);
return;
}
}
catch (CacheException ce)
{
if (LOG.isTraceEnabled())
{
LOG.trace(ce.getMessage(), ce);
}
// don't chain CacheException since Infinispan may not be on remote client's classpath
NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
ne.fillInStackTrace();
throw ne;
}
int size = name.size();
// get the context and key
Fqn ctx;
String key = name.get(size - 1);
boolean ctxIsRoot = false;
if (size > 1) // find subcontext to which the key is bound
{
String prefix = name.getPrefix(size - 1).toString();
Fqn fqn = Fqn.fromString(prefix);
ctx = Fqn.fromRelativeFqn(FQN_ROOT, fqn);
}
else
{
ctx = FQN_ROOT;
ctxIsRoot = true;
}
try
{
Object removed = this.cache.remove(ctx, key);
if (removed == null)
{
if (! ctxIsRoot && !this.cache.getRoot().hasChild(ctx))
{
throw new NotContextException(name.getPrefix(size - 1).toString() + " not a context");
}
throw new NameNotFoundException(key + " not bound");
}
}
catch (CacheException ce)
{
// don't chain CacheException since JBoss Cache may not be on remote client's classpath
NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
ne.setStackTrace(ce.getStackTrace());
throw ne;
}
}
@Override
public Object lookup(Name name) throws NamingException
{
boolean trace = LOG.isTraceEnabled();
if (trace)
{
LOG.trace("lookup, name="+name);
}
if (name.isEmpty())
{
// Return this
return new NamingContext(null, PARSER.parse(""), this.getHAStub());
}
// is the name a context?
try
{
Node<String, Binding> n = this.cache.getRoot().getChild(Fqn.fromRelativeFqn(FQN_ROOT, Fqn.fromString(name.toString())));
if (n != null)
{
Name fullName = (Name) name.clone();
return new NamingContext(null, fullName, this.getHAStub());
}
}
catch (CacheException ce)
{
if (LOG.isTraceEnabled())
{
LOG.trace(ce.getMessage(), ce);
}
// don't chain CacheException since Infinispan may not be on remote client's classpath
NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
ne.fillInStackTrace();
throw ne;
}
int size = name.size();
// get the context and key
Fqn ctx;
String key = name.get(size - 1);
if (size > 1) // find subcontext to which the key is bound
{
String prefix = name.getPrefix(size - 1).toString();
Fqn fqn = Fqn.fromString(prefix);
ctx = Fqn.fromRelativeFqn(FQN_ROOT, fqn);
}
else
{
ctx = FQN_ROOT;
}
try
{
Binding b = this.cache.get(ctx, key);
// if key not in cache, return null
return (b != null) ? b.getObject() : null;
}
catch (CacheException ce)
{
if (LOG.isTraceEnabled())
{
LOG.trace(ce.getMessage(), ce);
}
// don't chain CacheException since JBoss Cache may not be on remote client's classpath
NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
ne.fillInStackTrace();
throw ne;
}
}
@Override
public Collection<NameClassPair> list(Name name) throws NamingException
{
if (LOG.isTraceEnabled())
{
LOG.trace("list, name="+name);
}
// get the context
Fqn ctx;
String ctxName = "";
int size = name.size();
if (size >= 1)
{
ctxName = name.toString();
Fqn fqn = Fqn.fromString(ctxName);
ctx = Fqn.fromRelativeFqn(FQN_ROOT, fqn);
}
else
{
ctx = FQN_ROOT;
}
boolean exists = this.cache.getRoot().hasChild(ctx);
if (!exists)
{
try
{
return Collections.list(new InitialContext().list(name));
}
catch (NamingException e)
{
throw new NotContextException(ctxName+ " not a context");
}
}
try
{
List<NameClassPair> list = new LinkedList<NameClassPair>();
Node<String, Binding> base = this.cache.getRoot().getChild(ctx);
if (base != null)
{
for (Binding b: base.getData().values())
{
list.add(new NameClassPair(b.getName(),b.getClassName(),true));
}
// Why doesn't this return Set<String>?
Set<Object> children = base.getChildrenNames();
if (children != null && !children.isEmpty())
{
for (Object child: children)
{
String node = (String) child;
Name fullName = (Name) name.clone();
fullName.add(node);
list.add(new NameClassPair(node, NamingContext.class.getName(),true));
}
}
}
return list;
}
catch (CacheException ce)
{
if (LOG.isTraceEnabled())
{
LOG.trace(ce.getMessage(), ce);
}
// don't chain CacheException since JBoss Cache may not be on remote client's classpath
NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
ne.fillInStackTrace();
throw ne;
}
}
@Override
public Collection<Binding> listBindings(Name name) throws NamingException
{
if (LOG.isTraceEnabled())
{
LOG.trace("listBindings, name="+name);
}
// get the context
Fqn ctx;
String ctxName = "";
int size = name.size();
if (size >= 1)
{
ctxName = name.toString();
Fqn fqn = Fqn.fromString(ctxName);
ctx = Fqn.fromRelativeFqn(FQN_ROOT, fqn);
}
else
{
ctx = FQN_ROOT;
}
boolean exists = this.cache.getRoot().hasChild(ctx);
if (!exists)
{
// not found in global jndi, look in local.
try
{
return Collections.list(new InitialContext().listBindings(name));
}
catch (NamingException e)
{
throw new NotContextException(ctxName+ " not a context");
}
}
try
{
List<Binding> list = new LinkedList<Binding>();
Node<String, Binding> node = this.cache.getRoot().getChild(ctx);
if (node != null)
{
Map<String, Binding> data = node.getData();
if (data != null && !data.isEmpty())
{
list.addAll(data.values());
}
// Why doesn't this return Set<String>?
Set<Object> children = node.getChildrenNames();
if (children != null && !children.isEmpty())
{
for (Object obj: children)
{
String child = (String) obj;
Name fullName = (Name) name.clone();
fullName.add(child);
NamingContext subCtx = new NamingContext(null, fullName, this.getHAStub());
list.add(new Binding(child, NamingContext.class.getName(), subCtx, true));
}
}
}
return list;
}
catch (CacheException ce)
{
if (LOG.isTraceEnabled())
{
LOG.trace(ce.getMessage(), ce);
}
// don't chain CacheException since Infinispan may not be on remote client's classpath
NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
ne.fillInStackTrace();
throw ne;
}
}
@Override
public Context createSubcontext(Name name) throws NamingException
{
if (LOG.isTraceEnabled())
{
LOG.trace("createSubcontext, name="+name);
}
int size = name.size();
if (size == 0)
{
throw new InvalidNameException("Name is empty");
}
// does the new context already exist?
String str = name.toString();
Fqn fqn = Fqn.fromString(str);
Fqn ctx = Fqn.fromRelativeFqn(FQN_ROOT, fqn);
if (this.cache.getRoot().hasChild(ctx))
{
throw new NameAlreadyBoundException(str);
}
// does the prefix context already exist?
Fqn pctx;
String newctx = name.get(size - 1);
if (size > 1) // find subcontext to which the context will be added
{
String prefix = name.getPrefix(size - 1).toString();
Fqn fqn2 = Fqn.fromString(prefix);
pctx = Fqn.fromRelativeFqn(FQN_ROOT, fqn2);
boolean exists = this.cache.getRoot().hasChild(pctx);
if (!exists)
{
throw new NotContextException(name.getPrefix(size - 1).toString());
}
}
else
{
pctx = FQN_ROOT;
}
Fqn newf = Fqn.fromRelativeFqn(pctx, Fqn.fromString(newctx));
try
{
this.cache.put(newf, new HashMap<String, Binding>());
}
catch (CacheException ce)
{
if (LOG.isTraceEnabled())
{
LOG.trace(ce.getMessage(), ce);
}
// don't chain CacheException since JBoss Cache may not be on remote client's classpath
NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
ne.fillInStackTrace();
throw ne;
}
Name fullName = PARSER.parse("");
fullName.addAll(name);
return new NamingContext(null, fullName, this.getHAStub());
}
private void internalBind(Name name, Object obj, String className, boolean rebind) throws NamingException
{
if (name.isEmpty())
{ // Empty names are not allowed
throw new InvalidNameException("Name is empty");
}
int size = name.size();
// get the context and key
Fqn ctx;
String key = name.get(size - 1);
boolean isRootCtx = false;
if (size > 1) // find subcontext to which the key will be added
{
String prefix = name.getPrefix(size - 1).toString();
Fqn fqn = Fqn.fromString(prefix);
ctx = Fqn.fromRelativeFqn(FQN_ROOT, fqn);
boolean exists = this.cache.getRoot().hasChild(ctx);
if (!exists)
{
throw new NotContextException(name.getPrefix(size - 1).toString() + " not a context");
// note - NamingServer throws a CannotProceedException if the client attempts to bind
// to a Reference object having an "nns" address. This implementation simply
// throws the NotContextException that's used when "nns" isn't present.
}
}
else
{
ctx = FQN_ROOT;
isRootCtx = true;
}
if (LOG.isTraceEnabled())
{
LOG.trace("internalBind, name=" + name + ", class=" + className + ", ctx=" + ctx);
}
if (!rebind)
{
Node<String, Binding> node = this.cache.getRoot();
if(!isRootCtx) // search from context node otherwise search from root
{
node = node.getChild(ctx);
}
if ((node != null) && (node.get(key) != null))
{
throw new NameAlreadyBoundException(key);
}
}
this.cache.put(ctx, key, new Binding(key, className, obj, true));
}
}