/*---------------------------------------------------------------------------
* Speculoos, LDAP to objet mapping.
* Copyright (C) 2006 Norsys and oQube
*
* 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Created on 27 sept. 2005
* Author: Arnaud Bailly
* --------------------------------------------------------------------------*/
package speculoos.jndi;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import speculoos.core.Mapper;
import speculoos.core.MapperCollector;
import speculoos.core.MapperException;
import speculoos.jndi.mappers.JNDIMapper;
import speculoos.manager.MapperConfigurationException;
import speculoos.spi.MapperTechnicalException;
import speculoos.spi.Source;
import speculoos.utils.VariableSubstitution;
/**
* Base implementation for accessing JNDI/LDAP data source.
* <p>
* This base implementation uses on demand binding to directory context for
* providing connections to mappers. Subclasses may override this behavior by
* providing their own implementations of {@link #connect()} and
* {@link #close(DirContext)}: The former provides DirContext instance for use
* by the mapper while the latter releases this instance when it is released by
* the mapper.
* </p>
* <p>
* Methods {@link #create(String, Map)} and {@link #release(Mapper)} are
* synchronized to provide minimal thread isolation. Please note howerver that
* {@link speculoos.core.Mapper} instances are not supposed to be thread safe so
* it is up to the client to ensure proper concurrency management.
* </p>
* <p>
* Method {@link #add(String, JNDIMapper)} associates a symbolic name to a
* Mapper instance. This instance will then be used as <em>Prototype</em>
* object when JNDISource will be asked for creating a Mapper through method
* {@link #create(String, Map)}: Base object is <code>cloned</code> and
* initialized with a connection before being returned to the client. The method
* {@link #release(Mapper)} must be invoked at some later point in order for
* this source to release associated resources.
* </p>
* <p>
* Environment management is provided through the method
* {@link #addParameters(Map)} which allows incremental configuration of the
* environment this source operates in.
* </p>
*
* @author nono
* @version $Id: JNDISource.java 76 2005-10-26 14:12:23 +0200 (Wed, 26 Oct 2005)
* /C=FR/ST=Nord/L=Lille/O=Norsys SA/OU=UE/CN=Arnaud
* Bailly/emailAddress=abailly@norsys.fr $
*/
public class JNDISource implements Source, MapperCollector {
private static final Log log = LogFactory.getLog(JNDISource.class);
/* are we bound ? */
private boolean bound;
/* environment for variables substitution */
private Map parameters = new HashMap();
/* mappers map */
private Map mapperMatrix = new HashMap();
private VariableSubstitution subst = new VariableSubstitution();
/* current environment */
private Map current;
private String name;
/*
* store unassigned mappers - used in chains
*/
private List mappers = new ArrayList();
/**
* Create a new named JNDISource.
*
* @param name
* the name of the source. May not be null.
*/
public JNDISource(String name) {
this.name = name;
}
/**
* Default constructor for creating anonymous source.
*
*/
public JNDISource() {
this("");
}
/*
* (non-Javadoc)
*
* @see speculoos.core.core.spi.Source#start(java.util.Map)
*/
public void start(Map env) throws MapperConfigurationException {
/* instanciate parameters */
this.current = new HashMap(parameters);
/* instanciate this environment */
for (Iterator i = this.current.entrySet().iterator(); i.hasNext();) {
Map.Entry me = (Map.Entry) i.next();
String v = subst.replaceVars(env, (String) me.getValue());
me.setValue(v);
}
if (log.isDebugEnabled())
log.debug("[" + name + "] starting with environment " + current);
if (log.isInfoEnabled())
log.info("[" + name + "] started");
bound = true;
}
/**
* Create a context from this source. This method simply calls creation of a
* new initial context. It may be overriden in subclasses to offer more
* sophisticated connection handling (eg. pooling).
*
* @return a DirContext representing base node for operation on this source.
* @throws MapperTechnicalException
*/
protected JNDIConnection connect() throws MapperTechnicalException {
try {
return new SimpleJNDIConnectionImpl(new InitialDirContext(new Hashtable(current)));
} catch (NamingException e) {
throw new MapperTechnicalException(
"Error while trying to connect to source ", e);
}
}
/*
* (non-Javadoc)
*
* @see speculoos.core.core.spi.Source#stop()
*/
public void stop() {
bound = false;
current = null;
if (log.isInfoEnabled())
log.info("[" + name + "] stopped");
}
/*
* (non-Javadoc)
*
* @see speculoos.core.core.spi.Source#create(String, java.util.Map)
*/
public synchronized Mapper create(String m, Map environment)
throws MapperException {
if (!bound)
throw new IllegalStateException(
"This source is not opened for business. Come back later");
JNDIMapper base = (JNDIMapper) mapperMatrix.get(m);
if (base == null)
throw new IllegalArgumentException("Mapper named " + m
+ " is unknown");
JNDIMapper jm = (JNDIMapper) (base).clone();
/* sets the directory object to use for operations */
JNDIConnection dirc;
if (log.isDebugEnabled())
log.info("[" + name + "] creating mapper " + m);
try {
dirc = connect();
jm.setDirectory(dirc);
} catch (NamingException e) {
throw new MapperTechnicalException(
"Cannot instantiate mapper " + m, e);
}
/* sets the environment */
Map env = new HashMap(environment);
env.putAll(current);
jm.setEnvironment(env);
if (log.isDebugEnabled())
log.info("[" + name + "] created mapper " + m + " with env " + env);
return jm;
}
/*
* (non-Javadoc)
*
* @see speculoos.core.core.spi.Source#release(speculoos.core.core.Mapper)
*/
public void release(Mapper mapper) throws MapperException {
JNDIMapper jm = (JNDIMapper) mapper;
if (!mapperMatrix.containsKey(jm.getName()))
throw new MapperException("[" + name + "] unknown mapper " + jm
+ " in this source ");
jm.getDirectory().close();
/* clean state of object */
jm.clearDirectory();
/* clear environment */
jm.getEnvironment().clear();
if (log.isDebugEnabled())
log.info("[" + name + "] released mapper " + mapper.getName());
}
/**
* Adds a new mapper to this source with given identifier. This method
* automatically adds all validation rules in this source to given mapper.
*
* @param string
* identifier must be unique within this source or else previous
* mapper is replaced by this one.
* @param jm
* the mapper
*/
public void add(String string, JNDIMapper jm) {
mapperMatrix.put(string, jm);
jm.getInputChain().addMappers(mappers);
}
/*
* (non-Javadoc)
*
* @see speculoos.spi.Source#addParameters(java.util.Map)
*/
public void addParameters(Map env2) {
this.parameters.putAll(env2);
}
/**
* Returns the name of this source.
*
* @return name of the source.
*/
public String getName() {
return name;
}
/*
* (non-Javadoc)
*
* @see speculoos.core.MapperCollector#addMapper(speculoos.core.Mapper)
*/
public void addMapper(Mapper m) {
mappers.add(m);
}
/*
* (non-Javadoc)
*
* @see speculoos.core.MapperCollector#addMappers(java.util.Collection)
*/
public void addMappers(Collection col) {
mappers.addAll(col);
}
/*
* (non-Javadoc)
*
* @see speculoos.core.MapperCollector#getMappers()
*/
public Collection getMappers() {
return Collections.unmodifiableList(mappers);
}
/*
* Returns current environment. For internal use only.
*/
protected Map getCurrent() {
return current;
}
}