You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by he...@apache.org on 2007/12/21 17:03:49 UTC

svn commit: r606228 [1/4] - in /directory/sandbox/hennejg/odm/trunk/src: ./ main/ main/java/ main/java/org/ main/java/org/apache/ main/java/org/apache/directory/ main/java/org/apache/directory/odm/ main/java/org/apache/directory/odm/auth/ main/resource...

Author: hennejg
Date: Fri Dec 21 08:03:46 2007
New Revision: 606228

URL: http://svn.apache.org/viewvc?rev=606228&view=rev
Log:
Share project 'apache odm' into 'https://svn.apache.org/repos/asf/directory'

Added:
    directory/sandbox/hennejg/odm/trunk/src/
    directory/sandbox/hennejg/odm/trunk/src/main/
    directory/sandbox/hennejg/odm/trunk/src/main/java/
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/AttributeMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Cardinality.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ChildMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryException.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryFacade.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DiropLogger.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/EhCacheSecondLevelCache.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Filter.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/GroupMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/LDAPConnectionDescriptor.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToManyMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToOneMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Mapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/OneToManyMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RDNAttributeMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ReferenceAttributeMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackAction.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackException.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/SecondLevelCache.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Transaction.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/TypeMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/UnmodifiableHashtable.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Util.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/auth/
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/auth/CachingCallbackHandler.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/auth/UsernamePasswordHandler.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/main/resources/
    directory/sandbox/hennejg/odm/trunk/src/main/resources/org/
    directory/sandbox/hennejg/odm/trunk/src/main/resources/org/apache/
    directory/sandbox/hennejg/odm/trunk/src/main/resources/org/apache/directory/
    directory/sandbox/hennejg/odm/trunk/src/main/resources/org/apache/directory/odm/
    directory/sandbox/hennejg/odm/trunk/src/main/resources/org/apache/directory/odm/ldap-mapping.xml   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/
    directory/sandbox/hennejg/odm/trunk/src/test/java/
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/AbstractEmbeddedDirectoryTest.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/TestBasicMapping.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/TestCaching.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/Client.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/DirectoryObject.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/Group.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/HardwareType.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/Location.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/OrganizationalUnit.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/Profile.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/Properties.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/Property.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/User.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/java/org/apache/directory/odm/test/model/UserGroup.java   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/resources/
    directory/sandbox/hennejg/odm/trunk/src/test/resources/log4j.properties   (with props)
    directory/sandbox/hennejg/odm/trunk/src/test/resources/org/
    directory/sandbox/hennejg/odm/trunk/src/test/resources/org/apache/
    directory/sandbox/hennejg/odm/trunk/src/test/resources/org/apache/odm/
    directory/sandbox/hennejg/odm/trunk/src/test/resources/org/apache/odm/test/
    directory/sandbox/hennejg/odm/trunk/src/test/resources/org/apache/odm/test/GENERIC_RFC.xml   (with props)

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/AttributeMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/AttributeMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/AttributeMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/AttributeMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,364 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author levigo
+ */
+public class AttributeMapping implements Cloneable {
+	private static final Logger logger = LoggerFactory.getLogger(AttributeMapping.class);
+
+	protected final String fieldName;
+
+	private Class fieldType;
+
+	private Method getMethod;
+
+	private String getMethodName;
+
+	private Method setMethod;
+
+	private String setMethodName;
+
+	protected TypeMapping type;
+
+	protected Cardinality cardinality = Cardinality.MANY;
+
+	public AttributeMapping(String fieldName, String fieldType)
+			throws ClassNotFoundException {
+		this.fieldName = fieldName;
+		this.setFieldType(Class.forName(fieldType));
+	}
+
+	/**
+	 * @param targetName
+	 * @param tx
+	 */
+	protected void cascadeDelete(Name targetName, Transaction tx)
+			throws DirectoryException {
+		// nothing to do here
+	}
+
+	/**
+	 * @param o
+	 * @param tx TODO
+	 * @throws DirectoryException
+	 */
+	protected void cascadePostLoad(Object o, Transaction tx)
+			throws DirectoryException {
+		// nothing to do here
+	}
+
+	/**
+	 * @param o
+	 * @param tx
+	 * @param ctx TODO
+	 * @throws DirectoryException
+	 */
+	protected void cascadePostSave(Object o, Transaction tx, DirContext ctx)
+			throws DirectoryException {
+		// nothing to do
+	}
+
+	/**
+	 * @param o TODO
+	 * @param newDN
+	 * @param tx TODO
+	 * @param targetName
+	 * @throws NamingException
+	 */
+	public void cascadeRDNChange(Object o, String oldDN, String newDN,
+			Transaction tx) throws DirectoryException, NamingException {
+		// nothing to do here
+	}
+
+	/**
+	 * @return
+	 */
+	protected boolean checkNull(Attributes a) {
+		return null == a.get(fieldName);
+	}
+
+	/**
+	 * Dehydrates the attribute value into the supplied attribute container and
+	 * returns the value.
+	 * 
+	 * @param o
+	 * @param a
+	 * @return the dehydrated value
+	 * @throws DirectoryException
+	 * @throws NamingException
+	 */
+	public Object dehydrate(Object o, BasicAttributes a)
+			throws DirectoryException, NamingException {
+		if (logger.isDebugEnabled())
+			logger.debug("dehydrating " + fieldName + " for instance of "
+					+ o.getClass());
+
+		try {
+			final Object v = getValue(o);
+			return valueToAttributes(a, v);
+		} catch (final Exception e) {
+			throw new DirectoryException("Can't read attribute from object", e);
+		}
+	}
+
+	/**
+	 * @param a
+	 * @param v
+	 * @return
+	 */
+	protected Object valueToAttributes(BasicAttributes a, Object v) {
+		if (null != v)
+			if (fieldType.equals(String.class)) {
+				if (((String) v).length() > 0)
+					a.put(fieldName, v);
+			} else if (fieldType.equals(byte[].class)) {
+				if (((byte[]) v).length > 0)
+					a.put(fieldName, v);
+			} else
+				a.put(fieldName, v.toString());
+		return v;
+	}
+
+	protected Class getFieldType() {
+		return fieldType;
+	}
+
+	protected Method getGetter() throws NoSuchMethodException {
+		if (null == this.getMethod) {
+			if (null == getMethodName)
+				getMethodName = "get" + fieldName.substring(0, 1).toUpperCase()
+						+ fieldName.substring(1);
+			this.getMethod = getMethod(type.getMappedType(), getMethodName,
+					new Class[]{});
+		}
+		return getMethod;
+	}
+
+	/**
+	 * @param modelClass
+	 * @param getMethod2
+	 * @param classes
+	 * @return
+	 * @throws NoSuchMethodException
+	 */
+	protected Method getMethod(Class targetClass, String methodName,
+			Class[] parameterTypes) throws NoSuchMethodException {
+		NoSuchMethodException firstException = null;
+		while (null != targetClass) {
+			try {
+				return targetClass.getMethod(methodName, parameterTypes);
+			} catch (final NoSuchMethodException e) {
+				if (null == firstException)
+					firstException = e;
+			}
+
+			targetClass = targetClass.getSuperclass();
+		}
+
+		throw firstException;
+	}
+
+	protected Method getSetter() throws NoSuchMethodException {
+		if (null == this.setMethod) {
+			if (null == setMethodName)
+				setMethodName = "set" + fieldName.substring(0, 1).toUpperCase()
+						+ fieldName.substring(1);
+			this.setMethod = getMethod(type.getMappedType(), setMethodName,
+					new Class[]{getFieldType()});
+		}
+		return setMethod;
+	}
+
+	/**
+	 * @param o
+	 * @return
+	 * @throws IllegalAccessException
+	 * @throws InvocationTargetException
+	 * @throws NoSuchMethodException
+	 */
+	protected Object getValue(Object o) throws DirectoryException {
+		try {
+			return getGetter().invoke(o, new Object[]{});
+		} catch (final Exception e) {
+			throw new DirectoryException("Can't get value for " + this, e);
+		}
+	}
+
+	/**
+	 * @param o
+	 * @param a
+	 * @param tx TODO
+	 * @throws NamingException
+	 * @throws InvocationTargetException
+	 * @throws IllegalAccessException
+	 * @throws IllegalArgumentException
+	 * @throws NoSuchMethodException
+	 * @throws DirectoryException
+	 */
+	public void hydrate(Object o, Attributes a, Transaction tx)
+			throws DirectoryException {
+		if (logger.isDebugEnabled())
+			logger.debug("hydrating " + this + " for object of type " + o.getClass()
+					+ " from " + a);
+
+		if (checkNull(a))
+			return;
+
+		try {
+			setValue(o, valueFromAttributes(a, o, tx));
+		} catch (final DirectoryException e) {
+			throw e;
+		} catch (final Exception e) {
+			throw new DirectoryException("Can't hydrate attribute " + fieldName, e);
+		}
+	}
+
+	/**
+	 * @param instance
+	 * @throws DirectoryException
+	 */
+	protected void initNewInstance(Object instance) throws DirectoryException {
+		// nothing to do
+	}
+
+	/**
+	 * 
+	 */
+	protected void initPostLoad() {
+		// nothing to do
+	}
+
+	protected void setFieldType(Class fieldType) {
+		this.fieldType = fieldType;
+		this.getMethod = this.setMethod = null;
+	}
+
+	public void setGetMethod(String getMethodName) {
+		this.getMethodName = getMethodName;
+	}
+
+	public void setSetMethod(String setMethodName) {
+		this.setMethodName = setMethodName;
+	}
+
+	public void setTypeMapping(TypeMapping type) {
+		this.type = type;
+	}
+
+	/**
+	 * @param o
+	 * @param a
+	 * @param dn
+	 * @return
+	 * @throws DirectoryException
+	 * @throws IllegalAccessException
+	 * @throws InvocationTargetException
+	 * @throws NoSuchMethodException
+	 * @throws NamingException
+	 * @throws DirectoryException
+	 */
+	Object setValue(Object o, Object value) throws DirectoryException {
+		try {
+			return getSetter().invoke(o, new Object[]{value});
+		} catch (final Exception e) {
+			throw new DirectoryException("Can't set value for " + this, e);
+		}
+	}
+
+	/*
+	 * @see java.lang.Object#toString()
+	 */
+	@Override
+	public String toString() {
+		return "[AttributeMapping name=" + fieldName + " type=" + fieldType + "]";
+	}
+
+	/**
+	 * @param a
+	 * @param o TODO
+	 * @param tx TODO
+	 * @return
+	 * @throws NamingException
+	 * @throws DirectoryException
+	 */
+	protected Object valueFromAttributes(Attributes a, Object o, Transaction tx)
+			throws NamingException, DirectoryException {
+		final Attribute attribute = a.get(fieldName);
+		if (null != attribute) {
+			Object v = attribute.get();
+			if (null != v)
+				// handle various value types
+				if (fieldType.equals(Integer.class))
+					try {
+						v = new Integer(v.toString());
+					} catch (final NumberFormatException e) {
+						logger.error("Can't convert this value to an Integer: "
+								+ v.toString());
+						v = null;
+					}
+			return v;
+		} else
+			return null;
+	}
+
+	/**
+	 * @param o
+	 * @param tx
+	 * @throws DirectoryException
+	 */
+	protected void cascadePreSave(Object o, Transaction tx)
+			throws DirectoryException {
+		// nothing to be done for basic attributes
+	}
+
+	/*
+	 * @see java.lang.Object#clone()
+	 */
+	@Override
+	protected AttributeMapping clone() throws CloneNotSupportedException {
+		return (AttributeMapping) super.clone();
+	}
+
+	public void setCardinality(String cardinality) {
+		this.cardinality = Cardinality.valueOf(cardinality);
+	}
+
+	protected String getFieldName() {
+		return fieldName;
+	}
+
+	protected TypeMapping getTypeMapping() {
+		return type;
+	}
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/AttributeMapping.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Cardinality.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Cardinality.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Cardinality.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Cardinality.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+enum Cardinality {
+	ONE, ZERO_OR_ONE, ONE_OR_MANY, MANY
+}
\ No newline at end of file

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Cardinality.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ChildMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ChildMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ChildMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ChildMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,386 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author levigo
+ */
+public class ChildMapping extends AttributeMapping implements Serializable {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	private static final Logger logger = LoggerFactory
+			.getLogger(Cardinality.class);
+
+	private String filter;
+	private final Class childType;
+	private TypeMapping childMapping;
+
+	public ChildMapping(String fieldName, String fieldType)
+			throws ClassNotFoundException {
+		super(fieldName, fieldType);
+		this.childType = Class.forName(fieldType);
+
+		if (!Object.class.isAssignableFrom(this.childType))
+			throw new IllegalArgumentException("The field " + fieldName
+					+ " is not a subclass of Object");
+	}
+
+	/*
+	 * @see org.apache.directory.odm.AttributeMapping#initPostLoad()
+	 */
+	@Override
+	protected void initPostLoad() {
+		super.initPostLoad();
+		final TypeMapping child = type.getMapping().getMapping(childType);
+		if (null == child)
+			throw new IllegalStateException(this + ": no mapping for peer type "
+					+ childType);
+
+		this.childMapping = child;
+
+		child.addReferrer(this);
+	}
+
+	/*
+	 * @see org.openthinclient.common.directory.ldap.AttributeMapping#hydrate(java.lang.Object,
+	 *      javax.naming.directory.Attributes)
+	 */
+	@Override
+	public void hydrate(Object o, Attributes a, Transaction tx)
+			throws DirectoryException {
+		// nothing to do here
+	}
+
+	/*
+	 * @see org.openthinclient.common.directory.ldap.AttributeMapping#cascadePostLoad(java.lang.Object)
+	 */
+	@Override
+	protected void cascadePostLoad(final Object parent, Transaction tx)
+			throws DirectoryException {
+		if (cardinality == Cardinality.MANY
+				|| cardinality == Cardinality.ONE_OR_MANY)
+			setValue(parent, Proxy.newProxyInstance(parent.getClass()
+					.getClassLoader(), new Class[]{Set.class}, new InvocationHandler() {
+				private Object childSet;
+
+				public Object invoke(Object proxy, Method method, Object[] args)
+						throws Throwable {
+					if (null == childSet) {
+						final Transaction tx = new Transaction(type.getMapping());
+						try {
+							DiropLogger.LOG.logReadComment("LAZY LOAD: children for {0}",
+									fieldName);
+
+							childSet = loadChildren(parent, tx);
+
+							// set real loaded object to original instance.
+							setValue(parent, childSet);
+						} finally {
+							tx.commit();
+						}
+					}
+					return method.invoke(childSet, args);
+				};
+			}));
+		else
+			setValue(parent, loadChildren(parent, tx));
+	}
+
+	/**
+	 * Load the referenced children for the given parent.
+	 * 
+	 * @param parent the parent
+	 * @param tx the current transaction
+	 * @return
+	 * @throws DirectoryException
+	 */
+	private Object loadChildren(Object parent, Transaction tx)
+			throws DirectoryException {
+		final String dn = type.getDN(parent);
+
+		final Set set = childMapping.list(null != filter
+				? new Filter(filter, dn)
+				: null, dn, null, tx);
+
+		switch (cardinality){
+			case ONE :
+				if (set.size() == 0) {
+					logger.warn("No child for " + this
+							+ " with cardinality ONE found at " + dn + ", filter: " + filter);
+					return null;
+				}
+				// fall through!
+			case ZERO_OR_ONE :
+				if (set.size() == 0)
+					return null;
+				if (set.size() == 1)
+					return set.iterator().next();
+				throw new DirectoryException("More than one child for " + this
+						+ " with cardinality ONE found at " + dn + ", filter: " + filter);
+			case ONE_OR_MANY :
+				if (set.size() == 0)
+					throw new DirectoryException("No child for " + this
+							+ " with cardinality ONE_OR_MANY found at " + dn + ", filter: "
+							+ filter);
+				// fall through
+			case MANY :
+			default :
+				return set;
+		}
+	}
+
+	/*
+	 * @see org.openthinclient.common.directory.ldap.AttributeMapping#checkNull(javax.naming.directory.Attributes)
+	 */
+	@Override
+	protected boolean checkNull(Attributes a) {
+		return false;
+	}
+
+	@Override
+	public void setCardinality(String cardinality) {
+		super.setCardinality(cardinality);
+
+		if (this.cardinality == Cardinality.ONE_OR_MANY
+				|| this.cardinality == Cardinality.MANY)
+			setFieldType(Set.class);
+	}
+
+	public void setFilter(String filter) {
+		this.filter = filter;
+	}
+
+	/*
+	 * @see org.openthinclient.common.directory.ldap.AttributeMapping#initNewInstance(org.openthinclient.common.directory.Object)
+	 */
+	@Override
+	protected void initNewInstance(Object instance) throws DirectoryException {
+		if (cardinality == Cardinality.ONE
+				|| cardinality == Cardinality.ONE_OR_MANY) {
+			Object v = type.getMapping().create(childType);
+
+			if (cardinality == Cardinality.ONE_OR_MANY) {
+				final Set tmp = new HashSet();
+				tmp.add(v);
+				v = tmp;
+			}
+
+			setValue(instance, v);
+		}
+	}
+
+	/*
+	 * @see org.openthinclient.common.directory.ldap.AttributeMapping#dehydrate(org.openthinclient.common.directory.Object,
+	 *      javax.naming.directory.BasicAttributes)
+	 */
+	@Override
+	public Object dehydrate(Object o, BasicAttributes a)
+			throws DirectoryException {
+		// nothing to do
+		return null;
+	}
+
+	/*
+	 * @see org.openthinclient.common.directory.ldap.AttributeMapping#cascadePostSave(org.openthinclient.common.directory.Object)
+	 */
+	@Override
+	protected void cascadePostSave(Object o, Transaction tx, DirContext ctx)
+			throws DirectoryException {
+		switch (cardinality){
+			case ONE :
+				final Object child = getValue(o);
+				if (null == child)
+					throw new DirectoryException(
+							"No child for child mapping with cardinality ONE present");
+				save(o, child, tx);
+				break;
+			case ZERO_OR_ONE :
+				save(o, getValue(o), tx);
+				break;
+			case ONE_OR_MANY :
+				Set set = (Set) getValue(o);
+				if (Proxy.isProxyClass(set.getClass())) {
+					if (logger.isDebugEnabled())
+						logger.debug("Still got the dynamic proxy for " + o);
+				} else {
+					if (set.size() == 0)
+						throw new DirectoryException(
+								"No child for child mapping with cardinality ONE_OR_MANY present");
+					save(o, set, tx);
+				}
+				break;
+			case MANY :
+			default :
+				set = (Set) getValue(o);
+				if (Proxy.isProxyClass(set.getClass())) {
+					if (logger.isDebugEnabled())
+						logger.debug("Still got the dynamic proxy for " + o);
+				} else
+					save(o, set, tx);
+				break;
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.apache.directory.odm.AttributeMapping#cascadeRDNChange(java.lang.Object,
+	 *      javax.naming.Name, javax.naming.Name,
+	 *      org.apache.directory.odm.Transaction)
+	 */
+	@Override
+	public void cascadeRDNChange(Object parent, String oldDN, String newDN,
+			Transaction tx) throws DirectoryException, NamingException {
+		switch (cardinality){
+			case ONE :
+			case ZERO_OR_ONE :
+				final Object child = getValue(parent);
+				childMapping.handleParentNameChange(child, oldDN, newDN, tx);
+				break;
+
+			case ONE_OR_MANY :
+			case MANY :
+				final Set children = (Set) getValue(parent);
+				if (Proxy.isProxyClass(children.getClass()))
+					// still got the proxy - just replace it
+					cascadePostLoad(parent, tx);
+				else
+					for (final Object c : children)
+						childMapping.handleParentNameChange(c, oldDN, newDN, tx);
+				break;
+		}
+	}
+
+	/**
+	 * @param child
+	 * @throws DirectoryException
+	 */
+	private void save(Object parent, Set children, Transaction tx)
+			throws DirectoryException {
+		final TypeMapping parentMapping = getParentMapping(parent);
+
+		String parentDNrelative;
+		// String parentDNabsolute;
+		try {
+			parentDNrelative = type.getDirectoryFacade().makeRelativeName(
+					parentMapping.getDN(parent)).toString();
+			// parentDNabsolute = type.getDirectoryFacade().makeAbsoluteName(
+			// parentDNrelative).toString();
+		} catch (final NamingException e) {
+			throw new DirectoryException(
+					"Parent DN can't be turned into relative one", e);
+		}
+
+		final Set existing = type.getMapping().list(childType,
+				null != filter ? new Filter(filter, parentDNrelative) : null,
+				parentDNrelative, null);
+
+		// see SUITE-81
+		// FIXME: This will prevent saveNewObject() in TypeMapping.save():
+		// make sure that all child DNs are set. Otherwise we might try to do
+		// spurious saves
+		// for (final Object child : children)
+		// childMapping.ensureDNSet(child, parentDNabsolute, tx);
+
+		// sync existing children with the ones contained in the object.
+		final Set missing = new HashSet();
+		if (null != children)
+			missing.addAll(children);
+
+		for (final Iterator i = missing.iterator(); i.hasNext();)
+			if (existing.remove(i.next()))
+				i.remove();
+
+		// missing now has the missing ones, existing the ones to be
+		// removed
+		for (final Object object : existing)
+			childMapping.delete(object, tx);
+		for (final Object missingObject : missing)
+			childMapping.save(missingObject, parentDNrelative, tx);
+	}
+
+	// private void ensureDNSet(Object child, String baseDN, Transaction tx)
+	// throws DirectoryException {
+	// if (null == childMapping.getDN(child))
+	// try {
+	// childMapping.fillEmptyDN(child, tx.getContext(childMapping
+	// .getDirectoryFacade()), baseDN);
+	// } catch (final Exception e) {
+	// throw new DirectoryException("Can't fill DN", e);
+	// }
+	// }
+
+	/**
+	 * @param child
+	 * @param tx
+	 * @throws DirectoryException
+	 */
+	private void save(Object parent, Object child, Transaction tx)
+			throws DirectoryException {
+		final TypeMapping parentMapping = getParentMapping(parent);
+
+		final String parentDN = parentMapping.getDN(parent);
+
+		if (null == child) {
+			// child is null - just delete the existing children
+			final Set set = type.getMapping().list(childType,
+					null != filter ? new Filter(filter, parentDN) : null, parentDN, null);
+			for (final Object existingChild : set)
+				childMapping.delete(existingChild, tx);
+		} else
+			// just save it (let the type mapping for the child do the chores)
+			childMapping.save(child, parentDN, tx);
+	}
+
+	private TypeMapping getParentMapping(Object parent) {
+		final TypeMapping parentMapping = type.getMapping().getMapping(
+				parent.getClass(), type.getDirectoryFacade());
+		if (null == parentMapping)
+			throw new IllegalStateException("Parent " + parent.getClass() + " for "
+					+ this + " is not mapped");
+		return parentMapping;
+	}
+
+	/*
+	 * @see org.apache.directory.odm.AttributeMapping#toString()
+	 */
+	@Override
+	public String toString() {
+		return "[ChildMapping name=" + fieldName + ", cardinality=" + cardinality
+				+ ", type=" + childType + "]";
+	}
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ChildMapping.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryException.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryException.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryException.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryException.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+/**
+ * FIXME: the DirectoryException should probably just subclass from
+ * NameingException instead of wrapping one, most of the time.
+ * 
+ * @author levigo
+ */
+public class DirectoryException extends Exception {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * 
+   */
+  public DirectoryException() {
+    super();
+  }
+
+  /**
+   * @param message
+   * @param cause
+   */
+  public DirectoryException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  /**
+   * @param message
+   */
+  public DirectoryException(String message) {
+    super(message);
+  }
+
+  /**
+   * @param cause
+   */
+  public DirectoryException(Throwable cause) {
+    super(cause);
+  }
+
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryFacade.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryFacade.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryFacade.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryFacade.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,507 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.naming.AuthenticationException;
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+
+import org.apache.directory.odm.LDAPConnectionDescriptor.ConnectionMethod;
+import org.apache.directory.odm.LDAPConnectionDescriptor.DirectoryType;
+import org.apache.directory.odm.LDAPConnectionDescriptor.ProviderType;
+import org.apache.directory.odm.auth.CachingCallbackHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A facade providing useful services around an LDAP directory connection as
+ * described by the {@link LDAPConnectionDescriptor} supplied via the
+ * constructor.
+ * 
+ * The {@link DirectoryFacade} is immutable with respect to the connection it
+ * uses.
+ * 
+ * @author levigo
+ */
+public class DirectoryFacade {
+	private static final Logger logger = LoggerFactory.getLogger(DirectoryFacade.class);
+
+	private Name baseDNName;
+	private DirectoryType directoryType;
+	private Hashtable<Object, Object> ldapEnvironment;
+	private NameParser nameParser;
+	private final LDAPConnectionDescriptor connectionDescriptor;
+
+	private String dummyDN;
+
+	DirectoryFacade(LDAPConnectionDescriptor lcd) {
+		// make copy so that changes to the original descriptor don't end up
+		// affecting this facade.
+		this.connectionDescriptor = new LDAPConnectionDescriptor(lcd);
+	}
+
+	/**
+	 * Get the baseDN for the described context as a {@link Name}
+	 * 
+	 * @return
+	 * @throws NamingException
+	 */
+	public Name getBaseDNName() throws NamingException {
+		if (null == baseDNName)
+			baseDNName = getNameParser().parse(connectionDescriptor.getBaseDN());
+
+		return baseDNName;
+	}
+
+	public Hashtable<Object, Object> getLDAPEnv() throws NamingException {
+		if (null == ldapEnvironment) {
+			final Hashtable<Object, Object> env = new Hashtable<Object, Object>(
+					connectionDescriptor.getExtraEnv());
+			populateDefaultEnv(env);
+
+			switch (connectionDescriptor.getConnectionMethod()){
+				case PLAIN :
+					env.put(Context.SECURITY_AUTHENTICATION, "none");
+					break;
+				case SSL :
+					env.put(Context.SECURITY_PROTOCOL, "ssl");
+					break;
+				case START_TLS :
+					// not yet...
+					// LdapContext ctx = new InitialLdapContext(env, null);
+					// StartTlsResponse tls =
+					// (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
+					// SSLSession sess = tls.negotiate();
+					throw new IllegalArgumentException("Start TLS not yet supported");
+			}
+
+			switch (connectionDescriptor.getAuthenticationMethod()){
+				case NONE :
+					env.put(Context.SECURITY_AUTHENTICATION, "none");
+					break;
+				case SIMPLE :
+					env.put(Context.SECURITY_AUTHENTICATION, "simple");
+
+					final NameCallback nc = new NameCallback("Bind DN");
+					final PasswordCallback pc = new PasswordCallback("Password", false);
+					try {
+						connectionDescriptor.getCallbackHandler().handle(
+								new Callback[]{nc, pc});
+					} catch (final Exception e) {
+						throw new NamingException("Can't get authentication information: "
+								+ e);
+					}
+
+					env.put(Context.SECURITY_PRINCIPAL, nc.getName());
+					env.put(Context.SECURITY_CREDENTIALS, new String(pc.getPassword())
+							.getBytes());
+					break;
+				case SASL :
+					// not yet...
+					throw new IllegalArgumentException("SASL not yet supported");
+			}
+
+			env.put(Context.PROVIDER_URL, connectionDescriptor.getLDAPUrl());
+
+			try {
+				final InetAddress[] hostAddresses = InetAddress
+						.getAllByName(connectionDescriptor.getHostname());
+				final InetAddress localHost = InetAddress.getLocalHost();
+				for (final InetAddress element : hostAddresses)
+					if (element.isLoopbackAddress() || element.equals(localHost)) {
+						env.put(Mapping.PROPERTY_FORCE_SINGLE_THREADED, Boolean.TRUE);
+						break;
+					}
+			} catch (final UnknownHostException e) {
+				// should not happen
+				logger.error("Can't look up local interface address", e);
+			}
+
+			// create unmodifiable copy.
+			this.ldapEnvironment = new UnmodifiableHashtable<Object, Object>(env);
+		}
+
+		return ldapEnvironment;
+	}
+
+	/**
+	 * Return the {@link NameParser} for the context described by this connection
+	 * descriptor.
+	 * 
+	 * @return
+	 * @throws NamingException
+	 */
+	public NameParser getNameParser() throws NamingException {
+		if (null == nameParser) {
+			final DirContext ctx = createDirContext();
+			try {
+				nameParser = ctx.getNameParser("");
+			} finally {
+				ctx.close();
+			}
+		}
+
+		return nameParser;
+	}
+
+	/**
+	 * FIXME: the schema heuristic is, ... well, ... debatable.
+	 * 
+	 * @param d
+	 * @return
+	 * @throws NamingException
+	 * @throws MalformedURLException
+	 * @throws DirectoryException
+	 */
+	public DirectoryType guessDirectoryType() throws NamingException {
+		if (null == directoryType)
+			if (connectionDescriptor.getProviderType() == ProviderType.APACHE_DS_EMBEDDED) {
+				this.dummyDN = "DC=dummy";
+				directoryType = DirectoryType.GENERIC_RFC;
+			} else {
+				// try to determine the type of server. at this point we distinguish
+				// only rfc-style and MS-ADS style. temporarily switch to RootDSE.
+				final DirContext ctx = createRootDSEContext();
+				try {
+					// Apache DS?
+					String vendorName = "";
+					final Attribute vendorNameAttr = ctx.getAttributes("",
+							new String[]{"vendorName"}).get("vendorName");
+					if (null != vendorNameAttr)
+						vendorName = vendorNameAttr.get().toString();
+
+					if (vendorName.toUpperCase().startsWith("APACHE")) {
+						this.dummyDN = "DC=dummy";
+						return DirectoryType.GENERIC_RFC;
+					}
+
+					// MS-ADS style?
+					final Attributes attrs = ctx.getAttributes("",
+							new String[]{"dsServiceName"});
+					String nextattr = "";
+					// Get a list of the attributes
+					final NamingEnumeration enums = attrs.getIDs();
+					// Print out each attribute and its values
+					while (enums != null && enums.hasMore())
+						nextattr = (String) enums.next();
+					if (attrs.get(nextattr) == null) {
+						directoryType = DirectoryType.GENERIC_RFC;
+
+						if (logger.isDebugEnabled())
+							logger.debug("This is GENERIC_RFC");
+					} else {
+						final DirContext schema2 = (DirContext) ctx.getSchema("").lookup(
+								"ClassDefinition");
+						// List the contents of root
+						final NamingEnumeration bds = schema2.list("");
+						final boolean[] hasClassesR2 = new boolean[3];
+						final boolean[] hasClassesSFU = new boolean[4];
+
+						// check Classes
+						while (bds.hasMore()) {
+							final String s = ((NameClassPair) bds.next()).getName()
+									.toString();
+							// Classes 2003R2
+							if (s.equals("nisMap"))
+								hasClassesR2[0] = true;
+							if (s.equals("nisObject"))
+								hasClassesR2[1] = true;
+							if (s.equals("device"))
+								hasClassesR2[2] = true;
+							// Classes ADS with SFU
+							if (s.equals("msSFU30NisMap") || s.equals("msSFUNISMap"))
+								hasClassesSFU[0] = true;
+							if (s.equals("msSFU30NisObject") || s.equals("msSFUNisObject"))
+								hasClassesSFU[1] = true;
+							if (s.equals("msSFU30Ieee802Device")
+									|| s.equals("msSFUIeee802Device"))
+								hasClassesSFU[2] = true;
+							if (s.equals("msSFU30IpHost") || s.equals("msSFUIpHost"))
+								hasClassesSFU[3] = true;
+						}
+						if (hasClassesR2[0] == true && hasClassesR2[1] == true
+								&& hasClassesR2[2] == true) {
+							directoryType = DirectoryType.MS_2003R2;
+
+							if (logger.isDebugEnabled())
+								logger.debug("This is an MS ADS - MS_2003R2");
+
+							this.dummyDN = getDummyDN(ctx);
+						}
+						if (hasClassesSFU[0] == true && hasClassesSFU[1] == true
+								&& hasClassesSFU[2] == true && hasClassesSFU[3] == true) {
+							directoryType = DirectoryType.MS_SFU;
+
+							if (logger.isDebugEnabled())
+								logger.debug("This is an MS ADS - MS_SFU");
+
+							this.dummyDN = getDummyDN(ctx);
+						}
+					}
+					if (null == directoryType)
+						throw new NamingException("Unrecognized directory server");
+				} finally {
+					ctx.close();
+				}
+			}
+
+		return directoryType;
+	}
+
+	/**
+	 * Return whether the specified name resides within the described context.
+	 * 
+	 * @param name
+	 * @return
+	 * @throws NamingException
+	 */
+	public boolean contains(Name name) throws NamingException {
+		return name.startsWith(getBaseDNName());
+	}
+
+	public LdapContext createDirContext() throws NamingException {
+		while (true)
+			try {
+				return new InitialLdapContext(getLDAPEnv(), null);
+			} catch (final AuthenticationException e) {
+				if (connectionDescriptor.getCallbackHandler() instanceof CachingCallbackHandler) {
+					try {
+						((CachingCallbackHandler) connectionDescriptor.getCallbackHandler())
+								.purgeCache();
+					} catch (final Exception e1) {
+						// if this method call failed, we give up instead of
+						// retrying.
+						throw new NamingException("Authentication with directory failed: "
+								+ e1 + " (was: " + e + ")");
+					}
+					continue;
+				}
+				throw e;
+			}
+	}
+
+	private LdapContext createRootDSEContext() throws NamingException {
+		while (true)
+			try {
+				final Hashtable<Object, Object> env = new Hashtable<Object, Object>(
+						getLDAPEnv());
+				env.put(Context.PROVIDER_URL, getLDAPUrlForRootDSE());
+
+				return new InitialLdapContext(env, null);
+			} catch (final AuthenticationException e) {
+				if (connectionDescriptor.getCallbackHandler() instanceof CachingCallbackHandler) {
+					try {
+						((CachingCallbackHandler) connectionDescriptor.getCallbackHandler())
+								.purgeCache();
+					} catch (final Exception e1) {
+						// if this method call failed, we give up instead of
+						// retrying.
+						throw new NamingException("Authentication with directory failed: "
+								+ e1 + " (was: " + e + ")");
+					}
+					continue;
+				}
+				throw e;
+			}
+	}
+
+	/**
+	 * Populate the environment with a few default settings.
+	 * 
+	 * @param env
+	 */
+	private void populateDefaultEnv(Hashtable<Object, Object> env) {
+		env.put(Context.INITIAL_CONTEXT_FACTORY, connectionDescriptor
+				.getProviderType().getClassName());
+		env.put(Context.REFERRAL, connectionDescriptor.getReferralPreference());
+
+		// Enable connection pooling
+		env.put("com.sun.jndi.ldap.connect.pool", "true");
+		env.put(Context.BATCHSIZE, "100");
+	}
+
+	private String getLDAPUrlForRootDSE() {
+		switch (connectionDescriptor.getProviderType()){
+			case SUN :
+			default :
+				return (connectionDescriptor.getConnectionMethod() != ConnectionMethod.SSL
+						? "ldap"
+						: "ldaps")
+						+ "://"
+						+ connectionDescriptor.getHostname()
+						+ ":"
+						+ connectionDescriptor.getPortNumber();
+			case APACHE_DS_EMBEDDED :
+				return "";
+		}
+	}
+
+	/**
+	 * Determine a dummy-DN based on the first ROOT DSE name.
+	 * 
+	 * @param ctx
+	 * @return
+	 * @throws NamingException
+	 */
+	private String getDummyDN(DirContext ctx) throws NamingException {
+		final Attributes dummyMember = ctx.getAttributes("",
+				new String[]{"rootDomainNamingContext"});
+		String nextDummy = "";
+
+		// get the first root DSE name.
+		for (final NamingEnumeration e = dummyMember.getIDs(); e != null
+				&& e.hasMore();)
+			nextDummy = (String) e.next();
+
+		return dummyMember.get(nextDummy).get().toString();
+	}
+
+	/**
+	 * Adjust the case of the attribute names in the given name according to the
+	 * needs of the target directory. E.g. ActiveDirectory wants all upper-case
+	 * names.
+	 * 
+	 * FIXME: making use of the fact that the parsed names are actually
+	 * {@link LdapName}s could make this more efficient.
+	 * 
+	 * @param memberDN
+	 * @param connectionDescriptor
+	 * @return
+	 * @throws NamingException
+	 */
+	public String fixNameCase(String memberDN) throws NamingException {
+
+		if (!guessDirectoryType().requiresUpperCaseRDNAttributeNames())
+			return memberDN;
+
+		// use context's name parser to split the name into parts
+		final Name parsed = getNameParser().parse(memberDN);
+		Name adjusted = null;
+		for (final Enumeration<String> e = parsed.getAll(); e.hasMoreElements();) {
+			String part = e.nextElement();
+
+			final int idx = part.indexOf('=');
+			final char c[] = part.toCharArray();
+			for (int i = 0; i < idx; i++)
+				c[i] = Character.toUpperCase(c[i]);
+			part = new String(c);
+
+			if (null == adjusted)
+				adjusted = getNameParser().parse(part);
+			else
+				adjusted.add(part);
+		}
+
+		return adjusted.toString();
+	}
+
+	/**
+	 * @param name
+	 * @param connectionDescriptor
+	 * @return
+	 * @throws NamingException
+	 * 
+	 * FIXME: respect upper case DN requirements
+	 */
+	public Name makeAbsoluteName(String name) throws NamingException {
+		final Name parsedName = getNameParser().parse(name);
+
+		// if the name is relative, append ctx base dn
+		if (!contains(parsedName))
+			parsedName.addAll(0, getBaseDNName());
+
+		return parsedName;
+	}
+
+	/**
+	 * @param name
+	 * @param connectionDescriptor
+	 * @return
+	 * @throws NamingException
+	 * 
+	 * FIXME: respect upper case DN requirements
+	 */
+	public Name makeRelativeName(String name) throws NamingException {
+		final Name parsedName = getNameParser().parse(name);
+
+		// return name directly if it is not absolute
+		if (!parsedName.startsWith(getBaseDNName()))
+			return parsedName;
+
+		// don't remove suffix, if the connections base DN is zero-sized
+		if (getBaseDNName().size() == 0)
+			return parsedName;
+
+		return parsedName.getSuffix(getBaseDNName().size());
+	}
+
+	/**
+	 * Get the dummy member to be used in 1..n attributes for groups when the
+	 * group doesn't have a member.
+	 * 
+	 * @return
+	 * @throws NamingException
+	 */
+	public String getDummyMember() throws NamingException {
+		if (null == dummyDN)
+			guessDirectoryType(); // this'll initialize it!
+		return dummyDN;
+	}
+
+	/**
+	 * Return whether the given DN is a dummy DN.
+	 * 
+	 * @return
+	 */
+	public boolean isDummyMember(String dn) {
+		return dummyDN.equalsIgnoreCase(dn);
+	}
+
+	public boolean isReadOnly() {
+		return connectionDescriptor.isReadOnly();
+	}
+
+	public Name makeAbsoluteName(Name name) throws InvalidNameException {
+		// is name already absolute?
+		if (baseDNName.startsWith(name))
+			return name;
+
+		return ((Name) baseDNName.clone()).addAll(name);
+	}
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DirectoryFacade.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DiropLogger.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DiropLogger.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DiropLogger.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DiropLogger.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import java.text.MessageFormat;
+
+import javax.naming.Name;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The DiropLogger logs directory operations by formatting them as LDIF.
+ * 
+ * @author levigo
+ */
+public class DiropLogger {
+	/**
+	 * Logger to be used to log directory read operations
+	 */
+	private final Logger readLogger;
+
+	/**
+	 * Logger to be used to log directory write operations
+	 */
+	private final Logger writeLogger;
+
+	/** Singleton instance */
+	public static DiropLogger LOG = new DiropLogger(DiropLogger.class
+			.getPackage().getName()
+			+ ".DIROP");
+
+	private DiropLogger(String prefix) {
+		readLogger = LoggerFactory.getLogger(prefix + ".READ");
+		writeLogger = LoggerFactory.getLogger(prefix + ".WRITE");
+	}
+
+	public boolean isReadEnabled() {
+		return readLogger.isDebugEnabled();
+	}
+
+	public boolean isWriteEnabled() {
+		return writeLogger.isDebugEnabled();
+	}
+
+	public void logModify(Name dn, ModificationItem mods[], String comment) {
+		if (isWriteEnabled())
+			logModify(dn.toString(), mods, comment);
+	}
+
+	public void logModify(String dn, ModificationItem mods[], String comment) {
+		if (isWriteEnabled())
+			try {
+				writeLogger.debug("# " + comment);
+
+				writeLogger.debug("dn: " + dn);
+				writeLogger.debug("changetype: modify");
+				for (final ModificationItem mi : mods) {
+					final String id = mi.getAttribute().getID();
+					switch (mi.getModificationOp()){
+						case DirContext.ADD_ATTRIBUTE :
+							writeLogger.debug("add: " + id);
+							break;
+						case DirContext.REMOVE_ATTRIBUTE :
+							writeLogger.debug("remove: " + id);
+							break;
+						case DirContext.REPLACE_ATTRIBUTE :
+							writeLogger.debug("replace: " + id);
+							break;
+					}
+					for (final NamingEnumeration<?> e = mi.getAttribute().getAll(); e
+							.hasMore();)
+						writeLogger.debug(id + ": " + e.next());
+					writeLogger.debug("-");
+				}
+				writeLogger.debug("");
+			} catch (final Exception e) {
+				writeLogger.error("Can't log operation: ", e);
+			}
+	}
+
+	public void logAdd(Name dn, Attributes attributes, String comment) {
+		if (isWriteEnabled())
+			logAdd(dn.toString(), attributes, comment);
+	}
+
+	public void logAdd(String dn, Attributes attributes, String comment) {
+		if (isWriteEnabled())
+			try {
+				writeLogger.debug("# " + comment);
+
+				writeLogger.debug("dn: " + dn);
+				writeLogger.debug("changetype: add");
+				logAttributes(attributes);
+				writeLogger.debug("");
+			} catch (final NamingException e) {
+				writeLogger.error("Can't log operation: ", e);
+			}
+	}
+
+	public void logDelete(Name dn, String comment) {
+		if (isWriteEnabled())
+			logDelete(dn.toString(), comment);
+	}
+
+	public void logDelete(String dn, String comment) {
+		if (isWriteEnabled()) {
+			writeLogger.debug("# " + comment);
+
+			writeLogger.debug("dn: " + dn);
+			writeLogger.debug("changetype: delete");
+			writeLogger.debug("");
+		}
+	}
+
+	private void logAttributes(Attributes attributes) throws NamingException {
+		for (final NamingEnumeration<? extends Attribute> e = attributes.getAll(); e
+				.hasMore();) {
+			final Attribute a = e.next();
+			final String id = a.getID();
+			for (final NamingEnumeration<?> f = a.getAll(); f.hasMore();)
+				writeLogger.debug(id + ": " + f.next());
+		}
+	}
+
+	public void logModRDN(Name oldName, Name newName, String comment) {
+		if (isWriteEnabled()) {
+			writeLogger.debug("# " + comment);
+			writeLogger.debug("dn: " + oldName.toString());
+			writeLogger.debug("changetype: modrdn");
+			writeLogger.debug("newrdn: " + newName.get(newName.size() - 1));
+			writeLogger.debug("");
+		}
+	}
+
+	/**
+	 * Enable/disable the logging of reads/writes.
+	 * 
+	 * @param read set to <code>true</code> to enable read logging
+	 * @param write set to <code>true</code> to enable write logging
+	 */
+	public void enable(boolean read, boolean write) {
+		readLogger.warn("Can't enable/disable programmatically");
+		writeLogger.warn("Can't enable/disable programmatically");
+
+		// this worked under plain log4j, but no longer under SLF4J...
+		// readLogger.setLevel(read ? Level.DEBUG : Level.WARN);
+		// writeLogger.setLevel(write ? Level.DEBUG : Level.WARN);
+	}
+
+	public void logReadComment(String pattern, Object... args) {
+		if (isReadEnabled())
+			readLogger.debug(MessageFormat.format("# " + pattern, args));
+	}
+
+	public void logGetAttributes(Name name, String[] attributes, String comment) {
+		if (isReadEnabled())
+			logGetAttributes(name.toString(), attributes, comment);
+	}
+
+	public void logGetAttributes(String dn, String[] attributes, String comment) {
+		if (isReadEnabled()) {
+			readLogger.debug("# GET ATTRIBUTES: " + comment);
+			readLogger.debug("# dn: " + dn);
+			if (null != attributes) {
+				final StringBuilder sb = new StringBuilder("# fetching only: ");
+				for (final String s : attributes)
+					sb.append(s).append(" ");
+				readLogger.debug(sb.toString());
+			}
+			readLogger.debug("");
+		}
+	}
+
+	public void logSearch(String dn, String filter, Object filterArgs[],
+			SearchControls sc, String comment) {
+		if (isReadEnabled()) {
+			readLogger.debug("# SEARCH: " + comment);
+			readLogger.debug("# base: " + dn);
+
+			readLogger.debug("# filter: " + filter);
+			if (null != filterArgs)
+				for (final Object arg : filterArgs)
+					readLogger.debug("#    " + arg);
+
+			readLogger.debug("# scope: "
+					+ (sc.getSearchScope() == SearchControls.OBJECT_SCOPE ? "object" : sc
+							.getSearchScope() == SearchControls.ONELEVEL_SCOPE
+							? "onelevel"
+							: "sub") + " timelimit: " + sc.getTimeLimit() + " countlimit: "
+					+ sc.getCountLimit());
+			readLogger.debug("");
+		}
+	}
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/DiropLogger.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/EhCacheSecondLevelCache.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/EhCacheSecondLevelCache.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/EhCacheSecondLevelCache.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/EhCacheSecondLevelCache.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import java.io.IOException;
+
+import javax.naming.Name;
+import javax.naming.directory.Attributes;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+import net.sf.ehcache.hibernate.EhCache;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A SecondLevelCache implementation using {@link EhCache} as its backing store.
+ * 
+ * @author levigo
+ */
+public class EhCacheSecondLevelCache implements SecondLevelCache {
+	private static final Logger logger = LoggerFactory
+			.getLogger(EhCacheSecondLevelCache.class);
+
+	private Cache cache;
+
+	public EhCacheSecondLevelCache() {
+		try {
+			if (CacheManager.getInstance().cacheExists("mapping"))
+				cache = CacheManager.getInstance().getCache("mapping");
+			else {
+				cache = new Cache("mapping", 5000, false, false, 120, 120);
+				CacheManager.getInstance().addCache(cache);
+			}
+		} catch (final CacheException e) {
+			logger.error("Can't create cache. Caching is disabled", e);
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.apache.directory.odm.SecondLevelCache#getEntry(javax.naming.Name)
+	 */
+	public Attributes getEntry(Name name) {
+		if (null == cache || Mapping.disableCache)
+			return null;
+		try {
+			final Element element = cache.get(name);
+			if (null != element && logger.isDebugEnabled())
+				logger.debug("Global cache hit for " + name);
+			return (Attributes) (null != element ? element.getValue() : null);
+		} catch (final Throwable e) {
+			logger.warn("Can't get from cache", e);
+			return null;
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.apache.directory.odm.SecondLevelCache#purgeEntry(javax.naming.Name)
+	 */
+	public boolean purgeEntry(Name name) throws IllegalStateException {
+		if (null != cache)
+			return cache.remove(name);
+		return false;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.apache.directory.odm.SecondLevelCache#putEntry(javax.naming.Name,
+	 *      javax.naming.directory.Attributes)
+	 */
+	public void putEntry(Name name, Attributes a) {
+		if (null != cache) {
+			cache.put(new Element(name, a));
+			if (logger.isDebugEnabled())
+				logger.debug("Caching entry for " + name);
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.apache.directory.odm.SecondLevelCache#clear()
+	 */
+	public void clear() throws IllegalStateException, IOException {
+		if (null != cache)
+			cache.removeAll();
+	}
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/EhCacheSecondLevelCache.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Filter.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Filter.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Filter.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Filter.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author levigo
+ */
+public class Filter {
+  private static final Pattern SHIFT_PATTERN = Pattern
+      .compile("(.*?)(?:\\{(\\d+)\\}(.*?))*");
+
+  private String expression;
+  private Object args[];
+
+  public Filter(String expression, Object... args) {
+    this.expression = expression;
+    this.args = args;
+  }
+
+  public String getExpression(int argumentOffset) {
+    if (argumentOffset == 0) {
+      return expression;
+    }
+
+    Matcher m = SHIFT_PATTERN.matcher(expression);
+    assert m.matches() : "SHIFT_PATTERN doesn't match";
+
+    int groupCount = m.groupCount();
+    StringBuffer sb = new StringBuffer(m.group(1));
+    int i = 2;
+    while (i <= groupCount) {
+      int groupNumber = Integer.parseInt(m.group(i));
+      sb.append("{").append(groupNumber + argumentOffset).append("}");
+      i++;
+      if (i <= groupCount) {
+        sb.append(m.group(i));
+        i++;
+      }
+    }
+
+    return sb.toString();
+  }
+
+  public void fillArguments(Object target[], int argumentOffset) {
+    System.arraycopy(args, 0, target, argumentOffset, args.length);
+  }
+
+  public Object[] getArgs() {
+    return args;
+  }
+
+  public int getArgumentCount() {
+    return args.length;
+  }
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Filter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/GroupMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/GroupMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/GroupMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/GroupMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+
+/**
+ * This class maps a group type (group, groupOfNames, groupOfUniqueNames, etc.)
+ * where the group members are represented by a multi-valued attribute.
+ * 
+ * @author levigo
+ */
+public final class GroupMapping extends TypeMapping {
+	/**
+	 * The name of the attribute holding the member references
+	 */
+	private final String memberAttribute;
+	private AttributeMapping memberMapping;
+
+	/**
+	 * @param className
+	 * @param baseDN
+	 * @param searchFilter
+	 * @param objectClasses
+	 * @param canUpdate
+	 * @param keyClass
+	 * @throws Exception 
+	 */
+	public GroupMapping(String className, String baseDN, String searchFilter,
+			String objectClasses, String keyClass, String memberAttribute)
+			throws Exception {
+		super(className, baseDN, searchFilter, objectClasses, keyClass);
+		this.memberAttribute = memberAttribute;
+	}
+
+	public void addMembers(AttributeMapping memberAttribute) {
+		// we just handle the members like any other attribute. This may change in
+		// the future.
+		add(memberAttribute);
+	}
+
+	/**
+	 * Add a member to the mapped group. This method is called by the mapper of a
+	 * member object if it detects a missing association from this group to
+	 * itself.
+	 * 
+	 * @param group the group object
+	 * @param memberField the name of the member field
+	 * @param memberDN member's DN
+	 * @param tx current transaction
+	 * @throws DirectoryException
+	 * @throws NamingException
+	 */
+	void addMember(Object group, String memberField, String memberDN,
+			Transaction tx) throws DirectoryException, NamingException {
+		memberDN = getDirectoryFacade().fixNameCase(memberDN);
+
+		final DirContext ctx = tx.getContext(getDirectoryFacade());
+		final String groupDN = getDN(group);
+
+		final Name groupName = getDirectoryFacade().makeRelativeName(groupDN);
+
+		ModificationItem mod = null;
+
+		// if the member attribute requires a dummy and the dummy is present, remove
+		// it.
+		if (memberMapping.cardinality == Cardinality.ONE_OR_MANY
+				&& !memberField.equals("member") && !memberField.equals("memberOf")) {
+			final Attribute membersAttribute = ctx.getAttributes(groupName,
+					new String[]{memberField}).get(memberAttribute);
+
+			if (null != membersAttribute) {
+				final String dummy = getDirectoryFacade().getDummyMember();
+				if (membersAttribute.contains(dummy))
+					membersAttribute.remove(dummy);
+				membersAttribute.add(memberDN);
+
+				mod = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
+						membersAttribute);
+			}
+		}
+
+		if (null == mod)
+			mod = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(
+					memberField, memberDN));
+
+		final ModificationItem[] mods = new ModificationItem[]{mod};
+
+		DiropLogger.LOG.logModify(groupDN, mods, "add member to group");
+
+		ctx.modifyAttributes(getDirectoryFacade().makeRelativeName(groupDN), mods);
+	}
+
+	/**
+	 * Remove a member from the mapped group
+	 * 
+	 * @param group the group object
+	 * @param memberField the member field name
+	 * @param memberDN the member DN
+	 * @param tx current transaction
+	 * @throws DirectoryException
+	 * @throws NamingException
+	 */
+	void removeMember(Object group, String memberField, String memberDN,
+			Transaction tx) throws DirectoryException, NamingException {
+		memberDN = getDirectoryFacade().fixNameCase(memberDN);
+
+		final DirContext ctx = tx.getContext(getDirectoryFacade());
+		final String groupDN = getDN(group);
+
+		final Name groupName = getDirectoryFacade().makeRelativeName(groupDN);
+
+		ModificationItem mod = null;
+
+		// if the member attribute requires a dummy and the last member is removed,
+		// re-add dummy.
+		if (memberMapping.cardinality == Cardinality.ONE_OR_MANY
+				&& !memberField.equals("member") && !memberField.equals("memberOf")) {
+			final Attribute membersAttribute = ctx.getAttributes(groupName,
+					new String[]{memberField}).get(memberAttribute);
+
+			// members attribute not present - should not happen
+			if (null == membersAttribute)
+				mod = new ModificationItem(DirContext.ADD_ATTRIBUTE,
+						new BasicAttribute(memberField, getDirectoryFacade()
+								.getDummyMember()));
+			else {
+				membersAttribute.remove(memberDN);
+
+				// if we removed the last value, add the dummy member
+				if (membersAttribute.size() == 0)
+					membersAttribute.add(getDirectoryFacade().getDummyMember());
+
+				mod = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
+						membersAttribute);
+			}
+		}
+
+		if (null == mod)
+			mod = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
+					new BasicAttribute(memberField, memberDN));
+
+		final ModificationItem[] mods = new ModificationItem[]{mod};
+
+		DiropLogger.LOG.logModify(groupDN, mods, "remove member from group");
+
+		ctx.modifyAttributes(getDirectoryFacade().makeRelativeName(groupDN), mods);
+	}
+
+	public boolean isInDirectory(Object group, String memberField, String dn,
+			Transaction tx) throws NamingException, DirectoryException {
+		// to query if the Object is in the Directory or not
+		final DirContext ctx = tx.getContext(getDirectoryFacade());
+		final String groupDN = getDN(group);
+
+		final Attributes attrs = ctx.getAttributes(getDirectoryFacade()
+				.makeRelativeName(groupDN), new String[]{memberField});
+		final Attribute a = attrs.getAll().next();
+		int k = 0;
+		while (k <= a.size() - 1) {
+			final String as = a.get(k).toString();
+			if (as.equalsIgnoreCase(dn))
+				return true;
+			k++;
+		}
+		return false;
+	}
+
+	@Override
+	protected void initPostLoad() {
+		super.initPostLoad();
+
+		// make sure that the member attribute points to a OneToManyMapping
+		for (final AttributeMapping am : attributes)
+			if (am.fieldName.equals(memberAttribute))
+				if (am instanceof OneToManyMapping) {
+					this.memberMapping = am;
+					break;
+				} else
+					throw new IllegalStateException("MemberAttribute " + memberAttribute
+							+ " of GroupMapping " + getMappedType()
+							+ " is not mapped using one-to-many");
+
+		if (null == memberMapping)
+			throw new IllegalStateException("MemberAttribute " + memberAttribute
+					+ " of GroupMapping missing corresponding one-to-many mapping");
+	}
+
+	//
+	// @Override
+	// protected void updateAttributes(Attributes currentAttributes,
+	// Attribute currentValues, Attribute newValues,
+	// List<ModificationItem> mods, Object o) throws NamingException,
+	// DirectoryException {
+	// if (newValues.getID().equalsIgnoreCase(memberAttribute))
+	// updateMembers(currentValues, currentAttributes, newValues, mods, o);
+	// else
+	// super.updateAttributes(currentAttributes, currentValues, newValues, mods,
+	// o);
+	// }
+	//
+	// /**
+	// * Update the members-attribute.
+	// *
+	// * @param currentValues
+	// * @param currentAttributes
+	// * @param newValues
+	// * @param mods
+	// * @param o
+	// * @throws NamingException
+	// * @throws DirectoryException
+	// */
+	// private void updateMembers(Attribute currentValues,
+	// Attributes currentAttributes, Attribute newValues,
+	// List<ModificationItem> mods, Object o) throws NamingException,
+	// DirectoryException {
+	//
+	// final Group group = (Group) o;
+	//
+	// final Set members = group.getMembers();
+	// if (!Proxy.isProxyClass(members.getClass())) {
+	// final Attribute attributeToEdit = new BasicAttribute(newValues.getID());
+	//
+	// for (final Object member : members) {
+	// final TypeMapping memberMapping = getMapping().getMapping(
+	// member.getClass());
+	// String memberDn = memberMapping.getDN(member);
+	// memberDn = getDirectoryFacade().fixNameCase(memberDn);
+	// attributeToEdit.add(memberDn);
+	// }
+	// if (attributeToEdit.size() == 0)
+	// attributeToEdit.add(OneToManyMapping.getDUMMY_MEMBER());
+	//
+	// mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
+	// attributeToEdit));
+	// }
+	// }
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/GroupMapping.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/LDAPConnectionDescriptor.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/LDAPConnectionDescriptor.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/LDAPConnectionDescriptor.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/LDAPConnectionDescriptor.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,344 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.directory.odm;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.security.auth.callback.CallbackHandler;
+
+/**
+ * A simple structure object to hold information about an LDAP connection.
+ * 
+ * @author levigo
+ */
+public class LDAPConnectionDescriptor implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The LDAP service provider type to be used.
+	 */
+	public enum ProviderType {
+		/**
+		 * Default SUN provider
+		 */
+		SUN("com.sun.jndi.ldap.LdapCtxFactory"),
+
+		/**
+		 * Apache directory server in embedded mode.
+		 */
+		APACHE_DS_EMBEDDED("org.apache.directory.server.jndi.ServerContextFactory");
+
+		private final String className;
+
+		ProviderType(String className) {
+			this.className = className;
+		}
+
+		public String getClassName() {
+			return className;
+		}
+	}
+
+	/**
+	 * The connection method to be used
+	 */
+	public enum ConnectionMethod {
+		/**
+		 * Unencrypted
+		 */
+		PLAIN,
+
+		/**
+		 * Use Secure Socket Layer
+		 */
+		SSL,
+
+		/**
+		 * Use Start TLS
+		 */
+		START_TLS;
+	}
+
+	/**
+	 * The authentication method to use
+	 */
+	public enum AuthenticationMethod {
+		/**
+		 * Anonymous bind
+		 */
+		NONE,
+		/**
+		 * Username/password authentication
+		 */
+		SIMPLE,
+		/**
+		 * SASL
+		 */
+		SASL;
+	}
+
+	/**
+	 * The DirectoryType describes a directory server implementation.
+	 */
+	public enum DirectoryType {
+		/**
+		 * Microsoft Active Directory Windows 2003 R2
+		 */
+		MS_2003R2(true),
+		/**
+		 * Microsoft Active Directory + Services For Unix (SFU)
+		 */
+		MS_SFU(true),
+		/**
+		 * Generic RFC style directory (OpenLDAP, Apache DS, ...)
+		 */
+		GENERIC_RFC(false);
+
+		/**
+		 * Flag indicating whether the directory implementations uses all upper case
+		 * RDN names.
+		 */
+		private final boolean upperCaseRDNAttributeNames;
+
+		private DirectoryType(boolean upperCaseRDNAttributeNames) {
+			this.upperCaseRDNAttributeNames = upperCaseRDNAttributeNames;
+		}
+
+		/**
+		 * Return whether directories of this type require all RDN attribute names
+		 * to be upper case. I.e. <code>CN=foo,DC=bar,DC=baz</code> instead of
+		 * <code>cn=foo,dc=bar,dc=baz</code>.
+		 * 
+		 * @return
+		 */
+		public boolean requiresUpperCaseRDNAttributeNames() {
+			return upperCaseRDNAttributeNames;
+		}
+	}
+
+	private ProviderType providerType = ProviderType.SUN;
+
+	private ConnectionMethod connectionMethod;
+
+	private AuthenticationMethod authenticationMethod;
+
+	private String hostname;
+
+	private short portNumber;
+
+	private String baseDN = "";
+
+	private CallbackHandler callbackHandler;
+
+	private Hashtable<String, Object> extraEnv = new Hashtable<String, Object>();
+
+	// private String referralPreference = "ignore";
+	private final String referralPreference = "follow";
+
+	/**
+	 * Flag indicating whether this connection should be read-only. This is
+	 * currently only a hint and may or may not be honored by downstream classes.
+	 */
+	private boolean readOnly;
+
+	/**
+	 * Default constructor. Sets a few reasonable defaults.
+	 * 
+	 * @param connectionDescriptor
+	 */
+	public LDAPConnectionDescriptor() {
+		this.connectionMethod = ConnectionMethod.PLAIN;
+		this.authenticationMethod = AuthenticationMethod.NONE;
+		this.hostname = "localhost";
+		this.portNumber = -1; // getPortNumber() will figure out a default!
+		this.baseDN = "";
+	}
+
+	/**
+	 * Copy constructor
+	 * 
+	 * @param connectionDescriptor
+	 */
+	public LDAPConnectionDescriptor(LDAPConnectionDescriptor lcd) {
+		this.providerType = lcd.providerType;
+		this.connectionMethod = lcd.connectionMethod;
+		this.authenticationMethod = lcd.authenticationMethod;
+		this.hostname = lcd.hostname;
+		this.portNumber = lcd.portNumber;
+		this.baseDN = lcd.baseDN;
+		this.callbackHandler = lcd.callbackHandler;
+		this.extraEnv.putAll(lcd.extraEnv);
+		this.readOnly = lcd.readOnly;
+	}
+
+	public String getLDAPUrl() {
+		switch (providerType){
+			case SUN :
+			default :
+				return (connectionMethod != ConnectionMethod.SSL ? "ldap" : "ldaps")
+						+ "://" + hostname + ":" + getPortNumber() + "/" + baseDN;
+			case APACHE_DS_EMBEDDED :
+				return baseDN;
+		}
+	}
+
+	/*
+	 * @see java.lang.Object#hashCode()
+	 */
+	@Override
+	public int hashCode() {
+		try {
+			final Hashtable<String, Object> env = extraEnv;
+			int hashCode = 0;
+			for (final Enumeration i = env.keys(); i.hasMoreElements();) {
+				final Object key = i.nextElement();
+				if (null != key)
+					hashCode ^= key.hashCode();
+				else
+					hashCode ^= 98435234;
+				if (null != env.get(key))
+					hashCode ^= env.get(key).hashCode();
+				else
+					hashCode ^= 21876381;
+			}
+
+			return hashCode ^ portNumber ^ hostname.hashCode()
+					^ callbackHandler.hashCode() ^ connectionMethod.hashCode()
+					^ authenticationMethod.hashCode();
+		} catch (final Exception e) {
+			return -9999;
+		}
+	}
+
+	public void setAuthenticationMethod(AuthenticationMethod authenticationMethod) {
+		this.authenticationMethod = authenticationMethod;
+	}
+
+	public AuthenticationMethod getAuthenticationMethod() {
+		return authenticationMethod;
+	}
+
+	public void setBaseDN(String baseDN) {
+		this.baseDN = baseDN;
+	}
+
+	public String getBaseDN() {
+		return baseDN;
+	}
+
+	/**
+	 * Set the JAAS {@link CallbackHandler} used to supply authentication
+	 * information.
+	 * 
+	 * @param callbackHandler
+	 */
+	public void setCallbackHandler(CallbackHandler callbackHandler) {
+		this.callbackHandler = callbackHandler;
+	}
+
+	/**
+	 * Get the JAAS {@link CallbackHandler}.
+	 * 
+	 * @return
+	 */
+	public CallbackHandler getCallbackHandler() {
+		return callbackHandler;
+	}
+
+	public void setConnectionMethod(ConnectionMethod connectionMethod) {
+		this.connectionMethod = connectionMethod;
+	}
+
+	public ConnectionMethod getConnectionMethod() {
+		return connectionMethod;
+	}
+
+	/**
+	 * Set extra environment settings to use.
+	 * 
+	 * @param extraEnv
+	 */
+	public void setExtraEnv(Hashtable<String, Object> extraEnv) {
+		this.extraEnv = extraEnv;
+	}
+
+	/**
+	 * Get Hashtable of extra environment settings to use.
+	 * 
+	 * @return
+	 */
+	public Hashtable<String, Object> getExtraEnv() {
+		return extraEnv;
+	}
+
+	public void setHostname(String hostname) {
+		this.hostname = hostname;
+	}
+
+	public String getHostname() {
+		return hostname;
+	}
+
+	public void setPortNumber(short portNumber) {
+		this.portNumber = portNumber;
+	}
+
+	public short getPortNumber() {
+		if (portNumber > 0)
+			return portNumber;
+
+		if (connectionMethod == ConnectionMethod.SSL)
+			return 686;
+		else
+			return 389;
+	}
+
+	/**
+	 * @return
+	 */
+	public boolean isBaseDnSet() {
+		return getBaseDN() != null && getBaseDN().length() > 0;
+	}
+
+	public ProviderType getProviderType() {
+		return providerType;
+	}
+
+	public void setProviderType(ProviderType providerType) {
+		this.providerType = providerType;
+	}
+
+	public String getReferralPreference() {
+		return referralPreference;
+	}
+
+	public DirectoryFacade createDirectoryFacade() {
+		return new DirectoryFacade(this);
+	}
+
+	public void setReadOnly(boolean readOnly) {
+		this.readOnly = readOnly;
+	}
+
+	public boolean isReadOnly() {
+		return readOnly;
+	}
+}

Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/LDAPConnectionDescriptor.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain