Added Paul Harvey's Spring LDAP Object-Directory Mapper (ODM).

This commit is contained in:
Ulrik Sandberg
2010-01-24 00:58:28 +00:00
parent 1dcad955c9
commit 7c4c15e88a
67 changed files with 5856 additions and 1 deletions

173
odm/pom.xml Normal file
View File

@@ -0,0 +1,173 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-parent</artifactId>
<version>1.3.1.CI-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-odm</artifactId>
<packaging>jar</packaging>
<name>Spring LDAP ODM</name>
<description>Object Directory Mapping framework</description>
<url>http://springframework.org/ldap</url>
<inceptionYear>2009</inceptionYear>
<developers>
<developer>
<name>Paul Harvey</name>
<email>paul@pauls-place.me.uk</email>
<roles>
<role>Developer</role>
</roles>
<timezone>0</timezone>
</developer>
</developers>
<properties>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.springframework.ldap.odm.tools.SchemaToJava</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<configuration>
<forkMode>always</forkMode>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>2.4</version>
<configuration>
<targetJdk>${java.version}</targetJdk>
</configuration>
</plugin>
</plugins>
</reporting>
<repositories>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>${version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core-tiger</artifactId>
<version>${version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-test</artifactId>
<version>${version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<type>jar</type>
<scope>runtime</scope>
<exclusions>
<exclusion>
<artifactId>jmxri</artifactId>
<groupId>com.sun.jmx</groupId>
</exclusion>
<exclusion>
<artifactId>jms</artifactId>
<groupId>javax.jms</groupId>
</exclusion>
<exclusion>
<artifactId>jmxtools</artifactId>
<groupId>com.sun.jdmk</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jdepend</groupId>
<artifactId>jdepend</artifactId>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<type>jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,65 @@
package org.springframework.ldap.odm.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation describes the mapping of a Java field to an LDAP attribute.
* <p>
* The containing class must be annotated with {@link Entry}.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
* @see Entry
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Attribute {
/**
* The Type attribute indicates whether a field is regarded as binary based
* or string based by the LDAP JNDI provider.
*/
enum Type {
/**
* A string field - returned by the JNDI LDAP provider as a {@link java.lang.String}.
*/
STRING, /**
* A binary field - returned by the JNDI LDAP provider as a <code>byte[]</code>.
*/
BINARY
}
/**
* The LDAP attribute name that this field represents.
* <p>
* Defaults to "" in which case the Java field name is used as the LDAP attribute name.
*
* @return The LDAP attribute name.
*
*/
String name() default "";
/**
* Indicates whether this field is returned by the LDAP JNDI provider as a
* <code>String</code> (<code>Type.STRING</code>) or as a
* <code>byte[]</code> (<code>Type.BINARY</code>).
*
* @return Either <code>Type.STRING</code> to indicate a string attribute
* or <code>Type.BINARY</code> to indicate a binary attribute.
*/
Type type() default Type.STRING;
/**
* The LDAP syntax of the attribute that this field represents.
* <p>
* This optional value is typically used to affect the precision of conversion
* of values between LDAP and Java,
* see {@link org.springframework.ldap.odm.typeconversion.ConverterManager}
* and {@link org.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl}.
*
* @return The LDAP syntax of this attribute.
*/
String syntax() default "";
}

View File

@@ -0,0 +1,25 @@
package org.springframework.ldap.odm.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a Java class to be persisted in an LDAP directory.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entry {
/**
* A list of LDAP object classes that the annotated Java class represents.
* <p>
* All fields will be persisted to LDAP unless annotated {@link Transient}.
*
* @return A list of LDAP classes which the annotated Java class represents.
*/
String[] objectClasses();
}

View File

@@ -0,0 +1,23 @@
package org.springframework.ldap.odm.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a Java field as containing the Distinguished Name of an LDAP Entry.
* <p>
* The marked field must be of type {@link javax.naming.Name} and must <em>not</em>
* be annotated {@link Attribute}.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
* @see Attribute
* @see javax.naming.Name
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Id {
}

View File

@@ -0,0 +1,19 @@
package org.springframework.ldap.odm.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation identifies a field in an {@link Entry} annotated class that
* should <em>not</em> be persisted to LDAP.
*
* @author Paul Harvey <paul@pauls-place.me.uk>
*
* @see Entry
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transient {
}

View File

@@ -0,0 +1,9 @@
/**
* Provides a set of annotations to describe the mapping of a Java class to an LDAP entry.
* <p>
* These annotations are for use with {@link org.springframework.ldap.odm.core.OdmManager}.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.annotations;

View File

@@ -0,0 +1,20 @@
package org.springframework.ldap.odm.core;
import org.springframework.ldap.NamingException;
/**
* The root of the Spring LDAP ODM exception hierarchy.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
@SuppressWarnings("serial")
public class OdmException extends NamingException {
public OdmException(String message) {
super(message);
}
public OdmException(String message, Throwable e) {
super(message, e);
}
}

View File

@@ -0,0 +1,97 @@
package org.springframework.ldap.odm.core;
import java.util.List;
import javax.naming.Name;
import javax.naming.directory.SearchControls;
/**
* The OdmManager interface provides generic CRUD (create/read/update/delete)
* and searching operations against an LDAP directory.
* <p>
* Each managed Java class must be appropriately annotated using
* {@link org.springframework.ldap.odm.annotations}.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
* @see org.springframework.ldap.odm.annotations.Entry
* @see org.springframework.ldap.odm.annotations.Attribute
* @see org.springframework.ldap.odm.annotations.Id
* @see org.springframework.ldap.odm.annotations.Transient
*/
public interface OdmManager {
/**
* Read a named entry from the LDAP directory.
*
* @param <T> The Java type to return
* @param clazz The Java type to return
* @param dn The distinguished name of the entry to read from the LDAP directory.
* @return The entry as read from the directory
*
* @exception org.springframework.ldap.NamingException on error.
*/
<T> T read(Class<T> clazz, Name dn);
/**
* Create the given entry in the LDAP directory.
*
* @param entry The entry to be create, it must <em>not</em> already exist in the directory.
*
* @exception org.springframework.ldap.NamingException on error.
*/
void create(Object entry);
/**
* Update the given entry in the LDAP directory.
*
* @param entry The entry to update, it must already exist in the directory.
*
* @exception org.springframework.ldap.NamingException on error.
*/
void update(Object entry);
/**
* Delete an entry from the LDAP directory.
*
* @param entry The entry to delete, it must already exist in the directory.
*
* @exception org.springframework.ldap.NamingException on error.
*/
void delete(Object entry);
/**
* Find all entries in the LDAP directory of a given type.
*
* @param <T> The Java type to return
* @param clazz The Java type to return
* @param base The root of the sub-tree at which to begin the search.
* @param searchControls The scope of the search.
* @return All entries that are of the type represented by the given
* Java class
*
* @exception org.springframework.ldap.NamingException on error.
*/
<T> List<T> findAll(Class<T> clazz, Name base, SearchControls searchControls);
/**
* Search for entries in the LDAP directory.
* <p>
* Only those entries that both match the given search filter and
* are represented by the given Java class are returned
*
* @param <T> The Java type to return
* @param clazz The Java type to return
* @param base The root of the sub-tree at which to begin the search.
* @param filter An LDAP search filter.
* @param searchControls The scope of the search.
* @return All matching entries.
*
* @exception org.springframework.ldap.NamingException on error.
*
* @see <a href="http://java.sun.com/products/jndi/tutorial/basics/directory/filter.html">Sun's JNDI tutorial description of search filters.</a>
* @see <a href="http://www.rfc-editor.org/rfc/rfc4515.txt">LDAP: String Representation of Search Filters RFC.</a>
*/
<T> List<T> search(Class<T> clazz, Name base, String filter, SearchControls searchControls);
}

View File

@@ -0,0 +1,226 @@
package org.springframework.ldap.odm.core.impl;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Id;
/*
* Extract attribute meta-data from the @Attribute annotation, the @Id annotation
* and via reflection.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
/* package */ final class AttributeMetaData {
private static final CaseIgnoreString OBJECT_CLASS_ATTRIBUTE_CI=new CaseIgnoreString("objectclass");
// Name of the LDAP attribute from the @Attribute annotation
private CaseIgnoreString name;
// Syntax of the LDAP attribute from the @Attribute annotation
private String syntax;
// Whether this attribute is binary from the @Attribute annotation
private boolean isBinary;
// The Java field corresponding to this meta-data
private final Field field;
// The Java class of the field corresponding to this meta data
// This is the actual scalar type meaning that if the field is
// List<String> then the valueClass will be String
private Class<?> valueClass;
// Is this field annotated @Id
private boolean isId;
// Is this field multi-valued represented by a List
private boolean isList;
// Is this the objectClass attribute
private boolean isObjectClass;
// Extract information from the @Attribute annotation:
// syntax, isBinary, isObjectClass and name.
private boolean processAttributeAnnotation(Field field) {
// Default to no syntax specified
syntax = "";
// Default to a String based attribute
isBinary = false;
// Default name of attribute to the name of the field
name = new CaseIgnoreString(field.getName());
// We have not yet found the @Attribute annotation
boolean foundAnnotation=false;
// Grab the @Attribute annotation
Attribute attribute = field.getAnnotation(Attribute.class);
// Did we find the annotation?
if (attribute != null) {
// Pull attribute name, syntax and whether attribute is binary
// from the annotation
foundAnnotation=true;
String localAttributeName = attribute.name();
// Would be more efficient to use !isEmpty - but that then makes us Java 6 dependent
if (localAttributeName != null && localAttributeName.length()>0) {
name = new CaseIgnoreString(localAttributeName);
}
syntax = attribute.syntax();
isBinary = attribute.type() == Attribute.Type.BINARY;
}
isObjectClass=name.equals(OBJECT_CLASS_ATTRIBUTE_CI);
return foundAnnotation;
}
// Extract reflection information from the field:
// valueClass, isList
private void determineFieldType(Field field) {
// Determine the class of data stored in the field
Class<?> fieldType = field.getType();
// We support only lists for multi-valued attributes, as we must allow duplicate values
if (Set.class.isAssignableFrom(fieldType)) {
throw new MetaDataException(String.format("Only lists are allowed for multivlaued attributes, errpr in field %1$s in Entry class %2$s",
field, field.getDeclaringClass()));
}
isList = List.class.isAssignableFrom(fieldType);
valueClass=null;
if (!isList) {
// It's not a list so assume its single valued - so just take the field type
valueClass = fieldType;
} else {
// It's multi-valued - so we need to look at the signature in
// the class file to find the generic type - this is supported for class file
// format 49 and greater which corresponds to java 5 and later.
ParameterizedType paramType;
try {
paramType = (ParameterizedType)field.getGenericType();
} catch (ClassCastException e) {
throw new MetaDataException(String.format("Can't determine destination type for field %1$s in Entry class %2$s",
field, field.getDeclaringClass()), e);
}
Type[] actualParamArguments = paramType.getActualTypeArguments();
if (actualParamArguments.length == 1) {
if (actualParamArguments[0] instanceof Class) {
valueClass = (Class<?>)actualParamArguments[0];
} else {
if (actualParamArguments[0] instanceof GenericArrayType) {
// Deal with arrays
Type type=((GenericArrayType)actualParamArguments[0]).getGenericComponentType();
if (type instanceof Class) {
valueClass=Array.newInstance((Class<?>)type, 0).getClass();
}
}
}
}
}
// Check we have been able to determine the value class
if (valueClass==null) {
throw new MetaDataException(String.format("Can't determine destination type for field %1$s in class %2$s",
field, field.getDeclaringClass()));
}
}
// Extract information from the @Id annotation:
// isId
private boolean processIdAnnotation(Field field, Class<?> fieldType) {
// Are we dealing with the Id field?
isId=field.getAnnotation(Id.class)!=null;
if (isId) {
// It must be of type Name or a subclass of that of
if (!Name.class.isAssignableFrom(fieldType)) {
throw new MetaDataException(
String.format("The id field must be of type javax.naming.Name or a subclass that of in Entry class %1$s",
field.getDeclaringClass()));
}
}
return isId;
}
// Extract meta-data from the given field
public AttributeMetaData(Field field) {
this.field=field;
// Reflection data
determineFieldType(field);
// Data from the @Attribute annotation
boolean foundAttributeAnnotation=processAttributeAnnotation(field);
// Data from the @Id annotation
boolean foundIdAnnoation=processIdAnnotation(field, valueClass);
// Check that the field has not been annotated with both @Attribute and with @Id
if (foundAttributeAnnotation && foundIdAnnoation) {
throw new MetaDataException(
String.format("You may not specifiy an %1$s annoation and an %2$s annotation on the same field, error in field %3$s in Entry class %4$s",
Id.class, Attribute.class, field.getName(), field.getDeclaringClass()));
}
// If this is the objectclass attribute then it must be of type List<String>
if (isObjectClass() && (!isList() || valueClass!=String.class)) {
throw new MetaDataException(String.format("The type of the objectclass attribute must be List<String> in classs %1$s",
field.getDeclaringClass()));
}
}
public String getSyntax() {
return syntax;
}
public boolean isBinary() {
return isBinary;
}
public Field getField() {
return field;
}
public CaseIgnoreString getName() {
return name;
}
public boolean isList() {
return isList;
}
public boolean isId() {
return isId;
}
public boolean isObjectClass() {
return isObjectClass;
}
public Class<?> getValueClass() {
return valueClass;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("name=%1$s | field=%2$s | valueClass=%3$s | syntax=%4$s| isBinary=%5$s | isId=%6$s | isList=%7$s | isObjectClass=%8$s",
getName(), getField(), getValueClass().getName(), getSyntax(), isBinary(), isId(), isList(), isObjectClass());
}
}

View File

@@ -0,0 +1,32 @@
package org.springframework.ldap.odm.core.impl;
// A case independent String wrapper.
/* package */ final class CaseIgnoreString implements Comparable<CaseIgnoreString> {
private final String string;
private final int hashCode;
public CaseIgnoreString(String string) {
if (string == null)
throw new NullPointerException();
this.string = string;
hashCode = string.toUpperCase().hashCode();
}
public boolean equals(Object other) {
return other instanceof CaseIgnoreString &&
((CaseIgnoreString)other).string.equalsIgnoreCase(string);
}
public int hashCode() {
return hashCode;
}
public int compareTo(CaseIgnoreString other) {
CaseIgnoreString cis = (CaseIgnoreString)other;
return String.CASE_INSENSITIVE_ORDER.compare(string, cis.string);
}
public String toString() {
return string;
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.ldap.odm.core.impl;
import org.springframework.ldap.odm.core.OdmException;
/**
* Thrown to indicate that an instance is not suitable for persisting in the LDAP directory.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
@SuppressWarnings("serial")
public class InvalidEntryException extends OdmException {
public InvalidEntryException(String message) {
super(message);
}
public InvalidEntryException(String message, Throwable reason) {
super(message, reason);
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.ldap.odm.core.impl;
import org.springframework.ldap.odm.core.OdmException;
/**
* Thrown to indicate an error in the annotated meta-data.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
@SuppressWarnings("serial")
public class MetaDataException extends OdmException {
public MetaDataException(String message) {
super(message);
}
public MetaDataException(String message, Throwable reason) {
super(message, reason);
}
}

View File

@@ -0,0 +1,124 @@
package org.springframework.ldap.odm.core.impl;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import org.springframework.ldap.odm.annotations.Transient;
/*
* An internal class to process the meta-data and reflection data for an entry.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
/* package */ final class ObjectMetaData implements Iterable<Field> {
private static final Log LOG = LogFactory.getLog(ObjectMetaData.class);
private AttributeMetaData idAttribute;
private Map<Field, AttributeMetaData> fieldToAttribute = new HashMap<Field, AttributeMetaData>();
private Set<CaseIgnoreString> objectClasses = new HashSet<CaseIgnoreString>();
public Set<CaseIgnoreString> getObjectClasses() {
return objectClasses;
}
public AttributeMetaData getIdAttribute() {
return idAttribute;
}
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
public Iterator<Field> iterator() {
return fieldToAttribute.keySet().iterator();
}
public AttributeMetaData getAttribute(Field field) {
return fieldToAttribute.get(field);
}
public ObjectMetaData(Class<?> clazz) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Extracting metadata from %1$s", clazz));
}
// Get object class metadata - the @Entity annotation
Entry entity = (Entry)clazz.getAnnotation(Entry.class);
if (entity != null) {
// Default objectclass name to the class name unless it's specified
// in @Entity(name={objectclass1, objectclass2});
String[] localObjectClasses = entity.objectClasses();
if (localObjectClasses != null && localObjectClasses.length > 0 && localObjectClasses[0].length() > 0) {
for (String localObjectClass:localObjectClasses) {
objectClasses.add(new CaseIgnoreString(localObjectClass));
}
} else {
objectClasses.add(new CaseIgnoreString(clazz.getSimpleName()));
}
} else {
throw new MetaDataException(String.format("Class %1$s must have a class level %2$s annotation", clazz,
Entry.class));
}
// Check the class is final
if (!Modifier.isFinal(clazz.getModifiers())) {
LOG.warn(String.format("The Entry class %1$s should be declared final", clazz.getSimpleName()));
}
// Get field meta-data - the @Attribute annotation
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// So we can write to private fields
field.setAccessible(true);
// Skip transient and synthetic fields
if (field.getAnnotation(Transient.class) != null || field.isSynthetic()) {
continue;
}
AttributeMetaData currentAttributeMetaData=new AttributeMetaData(field);
if (currentAttributeMetaData.isId()) {
if (idAttribute!=null) {
// There can be only one id field
throw new MetaDataException(
String.format("You man have only one field with the %1$s annotation in class %2$s", Id.class, clazz));
}
idAttribute=currentAttributeMetaData;
}
fieldToAttribute.put(field, currentAttributeMetaData);
}
if (idAttribute == null) {
throw new MetaDataException(
String.format("All Entry classes must define a field with the %1$s annotation, error in class %2$s", Id.class,
clazz));
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Extracted metadata from %1$s as %2$s", clazz, this));
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("objectsClasses=%1$s | idField=%2$s | attributes=%3$s",
objectClasses, idAttribute.getName(), fieldToAttribute);
}
}

View File

@@ -0,0 +1,474 @@
package org.springframework.ldap.odm.core.impl;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.SearchControls;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.simple.ParameterizedContextMapper;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.odm.core.OdmException;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
/**
* An implementation of {@link org.springframework.ldap.odm.core.OdmManager} which
* uses {@link org.springframework.ldap.odm.typeconversion.ConverterManager} to
* convert between Java and LDAP representations of attribute values.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
public final class OdmManagerImpl implements OdmManager {
private static final Log LOG = LogFactory.getLog(OdmManagerImpl.class);
// The link to the LDAP directory
private final LdapOperations ldapTemplate;
// The converter manager to use to translate values between LDAP and Java
private final ConverterManager converterManager;
private static String OBJECT_CLASS_ATTRIBUTE="objectclass";
private static CaseIgnoreString OBJECT_CLASS_ATTRIBUTE_CI=new CaseIgnoreString(OBJECT_CLASS_ATTRIBUTE);
private static final class EntityData {
private final ObjectMetaData metaData;
private final String ocFilter;
private EntityData(ObjectMetaData metaData, String ocFilter) {
this.metaData=metaData;
this.ocFilter=ocFilter;
}
}
// A map of managed classes to to meta data about those classes
private final Map<Class<?>, EntityData> metaDataMap=new HashMap<Class<?>, EntityData>();
public OdmManagerImpl(ConverterManager converterManager,
ContextSource contextSource,
Set<Class<?>> managedClasses) {
this.converterManager=converterManager;
this.ldapTemplate=new LdapTemplate(contextSource);
if (managedClasses!=null) {
for (Class<?> managedClass: managedClasses) {
addManagedClass(managedClass);
}
}
}
public OdmManagerImpl(ConverterManager converterManager,
ContextSource contextSource) {
this(converterManager, contextSource, null);
}
private EntityData getEntityData(Class<?> managedClass) {
EntityData result=metaDataMap.get(managedClass);
if (result==null) {
throw new UnmanagedClassException(String.format("The %1$s class is not managed by this OdmManager", managedClass));
}
return result;
}
/**
* Adds an {@link org.springframework.ldap.odm.annotations} annotated class to the set
* managed by this OdmManager.
*
* @param managedClass The class to add to the managed set.
*/
public void addManagedClass(Class<?> managedClass) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Adding class %1$s to managed set", managedClass));
}
// Extract the meta-data from the class
ObjectMetaData metaData=new ObjectMetaData(managedClass);
// Check we can construct the target type - it must have a zero argument public constructor
try {
managedClass.getConstructor();
} catch (NoSuchMethodException e) {
throw new InvalidEntryException(String.format(
"The class %1$s must have a zero argument constructor to be an Entry", managedClass));
}
// Check we have all of the necessary converters for the class
for (Field field : metaData) {
AttributeMetaData attributeInfo = metaData.getAttribute(field);
if (!attributeInfo.isId() && !(attributeInfo.isObjectClass())) {
Class<?> jndiClass = (attributeInfo.isBinary()) ? byte[].class : String.class;
Class<?> javaClass = attributeInfo.getValueClass();
if (!converterManager.canConvert(jndiClass, attributeInfo.getSyntax(), javaClass)) {
throw new InvalidEntryException(String.format(
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
jndiClass, javaClass, field.getName(), managedClass));
}
if (!converterManager.canConvert(javaClass, attributeInfo.getSyntax(), jndiClass)) {
throw new InvalidEntryException(String.format(
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
javaClass, jndiClass, field.getName(), managedClass));
}
}
}
// Filter so we only read the object classes supported by the managedClass
AndFilter ocFilter = new AndFilter();
for (CaseIgnoreString oc : metaData.getObjectClasses()) {
ocFilter.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, oc.toString()));
}
metaDataMap.put(managedClass, new EntityData(metaData, ocFilter.encode()));
}
/*
* (non-Javadoc)
*
* @see org.springframework.ldap.odm.core.OdmManager#create(java.lang.Object)
*/
public <T> T read(Class<T> clazz, Name dn) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Reading Entry at - %s$1", dn));
}
getEntityData(clazz);
T result = clazz.cast(ldapTemplate.lookup(dn, new GenericContextMapper<T>(clazz)));
if (result==null) {
throw new OdmException(String.format("Entry %1$s has excess object classes", dn));
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Found entry - %s$1", result));
}
return result;
}
/*
* (non-Javadoc)
*
* @see org.springframework.ldap.odm.core.OdmManager#create(java.lang.Object)
*/
public void create(Object entry) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Creating entry - %s$1", entry));
}
DirContextAdapter context = new DirContextAdapter(getId(entry));
mapToContext(entry, context);
ldapTemplate.bind(context);
}
/*
* (non-Javadoc)
*
* @see org.springframework.ldap.odm.core.OdmManager#update(java.lang.Object, boolean)
*/
public void update(Object entry) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Updating entry - %s$1", entry));
}
DirContextAdapter context = new DirContextAdapter(getId(entry));
mapToContext(entry, context);
ldapTemplate.rebind(context);
}
/*
* (non-Javadoc)
*
* @see org.springframework.ldap.odm.core.OdmManager#delete(javax.naming.Name)
*/
public void delete(Object entry) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Deleting %s$1", entry));
}
// Just to check that this is a managed class
getEntityData(entry.getClass());
ldapTemplate.unbind(getId(entry));
}
private Name getId(Object entry) {
try {
return (Name)getEntityData(entry.getClass()).metaData.getIdAttribute().getField().get(entry);
} catch (Exception e) {
throw new InvalidEntryException(String.format("Can't get Id field from Entry %1$s", entry),
e);
}
}
/* (non-Javadoc)
* @see org.springframework.ldap.odm.core.OdmManager#search(java.lang.Class, javax.naming.Name, java.lang.String, javax.naming.directory.SearchControls)
*/
public <T> List<T> search(Class<T> managedClass, Name base, String filter, SearchControls scope) {
EntityData entityData=getEntityData(managedClass);
// Add a filter so we only read the object class we can deal with
String finalFilter = entityData.ocFilter;
if (filter != null && filter.length() != 0) {
StringBuilder fixedFilter = new StringBuilder();
fixedFilter.append("(&(").append(filter).append(")").append(entityData.ocFilter).append(")");
finalFilter = fixedFilter.toString();
}
// Search from the root if we are not told where to search from
Name localBase = base;
if (base == null || base.size() == 0) {
localBase = DistinguishedName.EMPTY_PATH;
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Searching - base=%1$s, finalFilter=%2$s, scope=%3$s", base, finalFilter, scope));
}
@SuppressWarnings("unchecked")
List<T> result = ldapTemplate.search(localBase, finalFilter, scope, new GenericContextMapper<T>(managedClass));
result.remove(null);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Found %1$s Entries - %2$s", result.size(), result));
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.ldap.odm.core.OdmManager#findAll(javax.naming.Name, javax.naming.directory.SearchControls)
*/
public <T> List<T> findAll(Class<T> managedClass, Name base, SearchControls scope) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Searching for all Entries with objectClass=%1$s, with base=%2$s, scope=%3$s",
getEntityData(managedClass).metaData.getObjectClasses(), base, scope));
}
return search(managedClass, base, null, scope);
}
/**
* Used to convert from Java representation of an Ldap Entry when writing to
* the Ldap directory
*
* @param entry - The entry to convert.
* @param context - The LDAP context to store the converted entry
* @throws javax.naming.NamingEnumeration on error.
*/
private void mapToContext(Object entry, DirContextOperations context) {
ObjectMetaData metaData=getEntityData(entry.getClass()).metaData;
// Object classes are set from the metadata obtained from the @Entity annotation
int numOcs=metaData.getObjectClasses().size();
CaseIgnoreString[] metaDataObjectClasses=metaData.getObjectClasses().toArray(new CaseIgnoreString[numOcs]);
String[] stringOcs=new String[numOcs];
for (int ocIndex=0; ocIndex<numOcs; ocIndex++) {
stringOcs[ocIndex]=metaDataObjectClasses[ocIndex].toString();
}
context.setAttributeValues(OBJECT_CLASS_ATTRIBUTE, stringOcs);
// Loop through each of the fields in the object to write to LDAP
for (Field field : metaData) {
// Grab the meta data for the current field
AttributeMetaData attributeInfo = metaData.getAttribute(field);
// We dealt with the object class field about, and the DN is set by the call to write the object to LDAP
if (!attributeInfo.isId() && !(attributeInfo.isObjectClass())) {
try {
// If this is a "binary" object the JNDI expects a byte[] otherwise a String
Class<?> targetClass = (attributeInfo.isBinary()) ? byte[].class : String.class;
// Multi valued?
if (!attributeInfo.isList()) {
// Single valued - get the value of the field
Object fieldValue = field.get(entry);
// Ignore null field values
if (fieldValue != null) {
// Convert the field value to the required type and write it into the JNDI context
context.setAttributeValue(attributeInfo.getName().toString(), converterManager.convert(fieldValue,
attributeInfo.getSyntax(), targetClass));
}
} else { // Multi-valued
// We need to build up a list of of the values
List<String> attributeValues = new ArrayList<String>();
// Get the list of values
Collection<?> fieldValues = (Collection<?>)field.get(entry);
// Ignore null lists
if (fieldValues != null) {
for (final Object o : fieldValues) {
// Ignore null values
if (o != null) {
attributeValues.add((String)converterManager.convert(o, attributeInfo.getSyntax(),
targetClass));
}
}
context.setAttributeValues(attributeInfo.getName().toString(), attributeValues.toArray());
}
}
} catch (IllegalAccessException e) {
throw new InvalidEntryException(String.format("Can't set attribute %1$s", attributeInfo.getName()),
e);
}
}
}
}
/**
* Used to convert from the JNDI LDAP representation of an Entry to the Java representation when reading from LDAP
*/
private class GenericContextMapper<T> implements ParameterizedContextMapper<T> {
private final Class<T> managedClass;
private GenericContextMapper(Class<T> managedClass) {
this.managedClass=managedClass;
}
// Called by Spring LDAP to do the conversion
/*
* (non-Javadoc)
*
* @see org.springframework.ldap.core.simple.ParameterizedContextMapper#mapFromContext(java.lang.Object)
*/
public T mapFromContext(Object object) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Converting to Java Entry class %1$s from %2$s", managedClass, object));
}
// The Java representation of the LDAP entry
T result = null;
// This is guaranteed by Spring LDAP to be a DirContextOperations
DirContextOperations context = (DirContextOperations)object;
ObjectMetaData metaData=getEntityData(managedClass).metaData;
try {
// The result class must have a zero argument constructor
result = managedClass.newInstance();
// Build a map of JNDI attribute names to values
Map<CaseIgnoreString, Attribute> attributeValueMap = new HashMap<CaseIgnoreString, Attribute>();
// Get a NamingEnumeration to loop through the JNDI attributes in the entry
Attributes attributes = context.getAttributes();
NamingEnumeration<? extends Attribute> attributesEnumeration = attributes.getAll();
// Loop through all of the JNDI attributes
while (attributesEnumeration.hasMoreElements()) {
Attribute currentAttribute = (Attribute)attributesEnumeration.nextElement();
// Add the current attribute to the map keyed on the lowercased (case indep) id of the attribute
attributeValueMap.put(new CaseIgnoreString(currentAttribute.getID()), currentAttribute);
}
// Now loop through all the fields in the Java representation populating it with values from the
// attributeValueMap
for (Field field : metaData) {
// Get the current field
AttributeMetaData attributeInfo = metaData.getAttribute(field);
// We deal with the Id field specially
if (!attributeInfo.isId()) {
// Not the ID - but is is multi valued?
if (!attributeInfo.isList()) {
// No - its single valued, grab the JNDI attribute that corresponds to the metadata on the
// current field
Attribute attribute = attributeValueMap.get(attributeInfo.getName());
// There is no guarantee that this attribute is present in the directory - so ignore nulls
if (attribute != null) {
// Grab the JNDI value
Object value = attribute.get();
// Check the value is not null
if (value != null) {
// Convert the JNDI value to its Java representation - this will throw if the
// conversion fails
Object convertedValue = converterManager.convert(value, attributeInfo.getSyntax(),
attributeInfo.getValueClass());
// Set it in the Java version
field.set(result, convertedValue);
}
}
} else { // We are dealing with a multi valued attribute
// We need to build up a list of values
List<Object> fieldValues = new ArrayList<Object>();
// Grab the attribute from the JNDI representation
Attribute currentAttribute = attributeValueMap.get(attributeInfo.getName());
// There is no guarantee that this attribute is present in the directory - so ignore nulls
if (currentAttribute != null) {
// Loop through the values of the JNDI attribute
NamingEnumeration<?> valuesEmumeration = currentAttribute.getAll();
while (valuesEmumeration.hasMore()) {
// Get the current value
Object value = valuesEmumeration.nextElement();
// Check the value is not null
if (value != null) {
// Convert the value to its Java representation and add it to our working list
fieldValues.add(converterManager.convert(value, attributeInfo.getSyntax(),
attributeInfo.getValueClass()));
}
}
}
// Now we need to set the List in to a Java object
field.set(result, fieldValues);
}
} else { // The id field
field.set(result, converterManager.convert(context.getDn(), attributeInfo.getSyntax(),
attributeInfo.getValueClass()));
}
}
// If this is the objectclass attribute then check that values correspond to the metadata we have
// for the Java representation
Attribute ocAttribute = attributeValueMap.get(OBJECT_CLASS_ATTRIBUTE_CI);
if (ocAttribute != null) {
// Get all object class values from the JNDI attribute
Set<CaseIgnoreString> objectClassesFromJndi = new HashSet<CaseIgnoreString>();
NamingEnumeration<?> objectClassesFromJndiEnum = ocAttribute.getAll();
while (objectClassesFromJndiEnum.hasMoreElements()) {
objectClassesFromJndi.add(new CaseIgnoreString((String)objectClassesFromJndiEnum.nextElement()));
}
// OK - checks its the same as the meta-data we have
if (!objectClassesFromJndi.equals(metaData.getObjectClasses())) {
// The items found has classes in addition to those searched for - so ditch it
return null;
}
} else {
throw new InvalidEntryException(String.format("No object classes were returned for class %1$s",
managedClass.getName()));
}
} catch (NamingException ne) {
throw new InvalidEntryException(String.format("Problem creating %1$s from LDAP Entry %2$s",
managedClass, object), ne);
} catch (IllegalAccessException iae) {
throw new InvalidEntryException(String.format(
"Could not create an instance of %1$s could not access field", managedClass.getName()), iae);
} catch (InstantiationException ie) {
throw new InvalidEntryException(String.format("Could not instantiate %1$s", managedClass), ie);
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Converted object - %1$s", result));
}
return result;
}
}
}

View File

@@ -0,0 +1,90 @@
package org.springframework.ldap.odm.core.impl;
import java.util.Set;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
/**
* A Spring Factory bean which creates {@link OdmManagerImpl} instances.
* <p>
* Typical configuration would appear as follows:
* <pre>
* &lt;bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean">
* &lt;property name="converterManager" ref="converterManager" />
* &lt;property name="contextSource" ref="contextSource" />
* &lt;property name="managedClasses">
* &lt;set>
* &lt;value>org.myorg.myldapentries.Person&lt;/value>
* &lt;value>org.myorg.myldapentries.OrganizationalUnit&lt;/value>
* &lt;/set>
* &lt;/property>
* &lt;/bean>
* </pre>
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public final class OdmManagerImplFactoryBean implements FactoryBean {
private ContextSource contextSource=null;
private Set<Class<?>> managedClasses=null;
private ConverterManager converterManager=null;
/**
* Set the ContextSource to use to interact with the LDAP directory.
* @param contextSource The ContextSource to use.
*/
public void setContextSource(ContextSource contextSource) {
this.contextSource=contextSource;
}
/**
* Set the list of {@link org.springframework.ldap.odm.annotations}
* annotated classes the OdmManager will process.
* @param managedClasses The list of classes to manage.
*/
public void setManagedClasses(Set<Class<?>> managedClasses) {
this.managedClasses=managedClasses;
}
/**
* Set the ConverterManager to use to convert between LDAP
* and Java representations of attributes.
* @param converterManager The ConverterManager to use.
*/
public void setConverterManager(ConverterManager converterManager) {
this.converterManager=converterManager;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public Object getObject() throws Exception {
if (contextSource==null) {
throw new FactoryBeanNotInitializedException("contextSource property has not been set");
}
if (managedClasses==null) {
throw new FactoryBeanNotInitializedException("managedClasses property has not been set");
}
if (converterManager==null) {
throw new FactoryBeanNotInitializedException("converterManager property has not been set");
}
return new OdmManagerImpl(converterManager, contextSource, managedClasses);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<?> getObjectType() {
return OdmManagerImpl.class;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,21 @@
package org.springframework.ldap.odm.core.impl;
import org.springframework.ldap.odm.core.OdmException;
/**
* Thrown when an OdmManager method is called with a class
* which is not being managed by the OdmManager.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
@SuppressWarnings("serial")
public class UnmanagedClassException extends OdmException {
public UnmanagedClassException(String message, Throwable reason) {
super(message, reason);
}
public UnmanagedClassException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,9 @@
/**
* Provides a single public class which implements {@link org.springframework.ldap.odm.core.OdmManager}.
* <p>
* The OdmManager implementation works in conjunction with {@link org.springframework.ldap.odm.typeconversion} to provide
* conversion between the representation of attributes in LDAP and in Java.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.core.impl;

View File

@@ -0,0 +1,10 @@
/**
* Provides an OdmManager interface for interaction with an LDAP directory.
* <p>
* Implementations of this interface are intended to be used in conjunction with classes
* annotated with {@link org.springframework.ldap.odm.annotations}.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.core;

View File

@@ -0,0 +1,127 @@
package org.springframework.ldap.odm.tools;
/**
* Simple value class to hold the schema of an attribute.
* <p>
* It is only public to allow Freemarker access.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public final class AttributeSchema {
private final String name;
private final String syntax;
private final boolean isMultiValued;
private final boolean isPrimitive;
private final String scalarType;
private final boolean isBinary;
private final boolean isArray;
public AttributeSchema(final String name, final String syntax, final boolean isMultiValued,
final boolean isPrimitive, final boolean isBinary, final boolean isArray, final String scalarType) {
this.name = name;
this.syntax = syntax;
this.isMultiValued = isMultiValued;
this.isPrimitive = isPrimitive;
this.scalarType = scalarType;
this.isBinary = isBinary;
this.isArray = isArray;
}
public boolean getIsArray() {
return isArray;
}
public boolean getIsBinary() {
return isBinary;
}
public boolean getIsPrimitive() {
return isPrimitive;
}
public String getScalarType() {
return scalarType;
}
public String getName() {
return name;
}
public String getSyntax() {
return syntax;
}
public boolean getIsMultiValued() {
return isMultiValued;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format(
"{ name=%1$s, syntax=%2$s, isMultiValued=%3$s, isPrimitive=%4$s, isBinary=%5$s, isArray=%6$s, scalarType=%7$s }",
name, syntax, isMultiValued, isPrimitive, isBinary, isArray, scalarType);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (isArray ? 1231 : 1237);
result = prime * result + (isBinary ? 1231 : 1237);
result = prime * result + (isMultiValued ? 1231 : 1237);
result = prime * result + (isPrimitive ? 1231 : 1237);
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((scalarType == null) ? 0 : scalarType.hashCode());
result = prime * result + ((syntax == null) ? 0 : syntax.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AttributeSchema other = (AttributeSchema) obj;
if (isArray != other.isArray)
return false;
if (isBinary != other.isBinary)
return false;
if (isMultiValued != other.isMultiValued)
return false;
if (isPrimitive != other.isPrimitive)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (scalarType == null) {
if (other.scalarType != null)
return false;
} else if (!scalarType.equals(other.scalarType))
return false;
if (syntax == null) {
if (other.syntax != null)
return false;
} else if (!syntax.equals(other.syntax))
return false;
return true;
}
}

View File

@@ -0,0 +1,91 @@
package org.springframework.ldap.odm.tools;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Simple value class to hold the schema of an object class
* <p>
* It is public only to allow Freemarker access.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public final class ObjectSchema {
private final Set<AttributeSchema> must = new HashSet<AttributeSchema>();
private final Set<AttributeSchema> may = new HashSet<AttributeSchema>();
private final Set<String> objectClass = new HashSet<String>();
public void addMust(AttributeSchema must) {
this.must.add(must);
}
public void addMay(AttributeSchema may) {
this.may.add(may);
}
public void addObjectClass(String objectClass) {
this.objectClass.add(objectClass);
}
public Set<AttributeSchema> getMust() {
return Collections.unmodifiableSet(must);
}
public Set<AttributeSchema> getMay() {
return Collections.unmodifiableSet(may);
}
public Set<String> getObjectClass() {
return Collections.unmodifiableSet(objectClass);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("objectClass=%1$s | must=%2$s | may=%3$s", objectClass, must, may);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((may == null) ? 0 : may.hashCode());
result = prime * result + ((must == null) ? 0 : must.hashCode());
result = prime * result + ((objectClass == null) ? 0 : objectClass.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ObjectSchema other = (ObjectSchema) obj;
if (may == null) {
if (other.may != null)
return false;
} else if (!may.equals(other.may))
return false;
if (must == null) {
if (other.must != null)
return false;
} else if (!must.equals(other.must))
return false;
if (objectClass == null) {
if (other.objectClass != null)
return false;
} else if (!objectClass.equals(other.objectClass))
return false;
return true;
}
}

View File

@@ -0,0 +1,157 @@
package org.springframework.ldap.odm.tools;
import java.util.HashSet;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.springframework.ldap.odm.tools.SyntaxToJavaClass.ClassInfo;
// Processes LDAP Schema
/* package */ final class SchemaReader {
private final DirContext schemaContext;
private final SyntaxToJavaClass syntaxToJavaClass;
private final Set<String> binarySet;
public SchemaReader(DirContext schemaContext, SyntaxToJavaClass syntaxToJavaClass, Set<String> binarySet) {
this.schemaContext = schemaContext;
this.syntaxToJavaClass = syntaxToJavaClass;
this.binarySet = binarySet;
}
// Get the object schema for the given object classes
public ObjectSchema getObjectSchema(Set<String> objectClasses)
throws NamingException, ClassNotFoundException {
ObjectSchema result = new ObjectSchema();
createObjectClass(objectClasses, schemaContext, result);
return result;
}
private enum SchemaAttributeType {
SUP, MUST, MAY, UNKNOWN
}
private SchemaAttributeType getSchemaAttributeType(String type) {
SchemaAttributeType result = SchemaAttributeType.UNKNOWN;
if (type.equals("SUP")) {
result = SchemaAttributeType.SUP;
} else {
if (type.equals("MUST")) {
result = SchemaAttributeType.MUST;
} else {
if (type.equals("MAY")) {
result = SchemaAttributeType.MAY;
}
}
}
return result;
}
private AttributeSchema createAttributeSchema(String name, DirContext schemaContext)
throws NamingException, ClassNotFoundException {
// Get the schema definition
Attributes attributeSchema = schemaContext.getAttributes("AttributeDefinition/" + name);
// Get the syntax - ditching any trailing length value that { and whatever follows it
String syntax = ((String)attributeSchema.get("SYNTAX").get()).split("\\{")[0];
// Is it binary?
boolean isBinary=binarySet.contains(syntax);
// Use it to look up the required Java class
ClassInfo classInfo = syntaxToJavaClass.getClassInfo(syntax);
// Now we can set the java class
String javaClassName = null;
boolean isPrimitive = false;
boolean isArray = false;
if (classInfo!=null) {
javaClassName=classInfo.getClassName();
Class<?> javaClass=Class.forName(classInfo.getFullClassName());
javaClassName=javaClass.getSimpleName();
isPrimitive=javaClass.isPrimitive();
isArray=javaClass.isArray();
} else {
if (isBinary) {
javaClassName="byte[]";
isPrimitive=false;
isArray=true;
} else {
javaClassName="String";
isPrimitive=false;
isArray=false;
}
}
return new AttributeSchema(name, syntax,
attributeSchema.get("SINGLE-VALUE") == null,
isPrimitive, isBinary, isArray, javaClassName);
}
// Recursively extract schema from the directory and process it
private void createObjectClass(Set<String> objectClasses, DirContext schemaContext, ObjectSchema schema)
throws NamingException, ClassNotFoundException {
// Super classes
Set<String> supList = new HashSet<String>();
// For each of the given object classes
for (String objectClass : objectClasses) {
// Add to set of included object classes
schema.addObjectClass(objectClass);
// Grab the LDAP schema of the object class
Attributes attributes = schemaContext.getAttributes("ClassDefinition/" + objectClass);
NamingEnumeration<? extends Attribute> valuesEnumeration = attributes.getAll();
// Loop through each of the attributes
while (valuesEnumeration.hasMoreElements()) {
Attribute currentAttribute = valuesEnumeration.nextElement();
// Get the attribute name and lower case it (as this is all case indep)
String currentId = currentAttribute.getID().toUpperCase();
// Is this a MUST, MAY or SUP attribute
SchemaAttributeType type = getSchemaAttributeType(currentId);
// Loop through all the values
NamingEnumeration<?> currentValues = currentAttribute.getAll();
while (currentValues.hasMoreElements()) {
String currentValue = (String)currentValues.nextElement();
switch (type) {
case SUP:
// Its a super class
String lowerCased=currentValue.toLowerCase();
if (!schema.getObjectClass().contains(lowerCased)) {
supList.add(lowerCased);
}
break;
case MUST:
// Add must attribute
schema.addMust(createAttributeSchema(currentValue, schemaContext));
break;
case MAY:
// Add may attribute
schema.addMay(createAttributeSchema(currentValue, schemaContext));
break;
default:
// Nothing to do
}
}
}
// Recurse for super classes
createObjectClass(supList, schemaContext, schema);
}
}
}

View File

@@ -0,0 +1,442 @@
package org.springframework.ldap.odm.tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* This tool creates a Java class representation of a set of LDAP object classes for use
* with {@link org.springframework.ldap.odm.core.OdmManager}.
* <p>
* The schema of a named list of object classes is read from an LDAP directory and used
* to generate a representative Java class. The Java class is automatically annotated with
* {@link org.springframework.ldap.odm.annotations} for use with
* {@link org.springframework.ldap.odm.core.OdmManager}.
* <p>
* The mapping of LDAP attributes to their Java representations may be configured by supplying the
* <code>-s</code> flag or the equivalent <code>--syntaxmap</code> flag whose argument is
* the name of a file with the following structure:
* <pre>
* # List of attribute syntax to java class mappings
*
* # Syntax Java class
* # ------ ----------
*
* 1.3.6.1.4.1.1466.115.121.1.50, java.lang.Integer
* 1.3.6.1.4.1.1466.115.121.1.40, some.other.Class
* </pre>
* <p>
* Syntaxes not included in this map will be represented as {@link java.lang.String} if they are returned as Strings by the
* JNDI LDAP provider and will be represented as <code>byte[]</code> if they are returned by the provider as <code>byte[]</code>.
* <p>
* Command line flags are as follows:
* <p>
* <ul>
* <li><code>-c,--class &lt;class name></code> Name of the Java class to create. Mandatory.</li>
* <li><code>-s,--syntaxmap &lt;map file></code> Configuration file of LDAP syntaxes to Java classes mappings. Optional.</li>
* <li><code>-h,--help</code> Print this help message then exit.</li>
* <li><code>-k,--package &lt;package name></code> Package to create the Java class in. Mandatory.</li>
* <li><code>-l,--url &lt;ldap url></code> Ldap url of the directory service to bind to. Defaults to <code>ldap://127.0.0.1:389</code>. Optional.</li>
* <li><code>-o,--objectclasses &lt;LDAP object class lists></code> Comma separated list of LDAP object classes. Mandatory.</li>
* <li><code>-u,--username &lt;dn></code> DN to bind with. Defaults to "". Optional.</li>
* <li><code>-p,--password &lt;password></code> Password to bind with. Defaults to "". Optional.</li>
* <li><code>-t,--outputdir &lt;output directory></code> Base output directory, defaults to ".". Optional.</li>
* </ul>
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
public final class SchemaToJava {
private static Log LOG = LogFactory.getLog(SchemaToJava.class);
// Name of the FreeMarker template used to generate the Java code.
private static String TEMPLATE_FILE = "oc-to-java.ftl";
// Name of file containing the list of attributes syntaxes to
// returned as byte[] by the JNDI LDAP provider.
private static String BINARY_FILE = "binary-attributes.txt";
// Class to use a base for loading resources
private static final Class<?> loaderClass=SchemaToJava.class;
// Default LDAP Url to bind with
private static final String DEFAULT_URL="ldap://127.0.0.1:389";
// Command line flags
private enum Flag {
URL("l", "url"),
USERNAME("u", "username"),
PASSWORD("p", "password"),
OBJECTCLASS("o", "objectclasses"),
CLASS("c", "class"),
PACKAGE("k", "package"),
SYNTAX_MAP("s", "syntaxmap"),
OUTPUT_DIR("t", "outputdir"),
HELP("h", "help");
private String shortName;
private String longName;
private Flag(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
public String getShort() {
return shortName;
}
public String getLong() {
return longName;
}
@Override
public String toString() {
return String.format("short=%1$s, long=%2$s", shortName, longName);
}
}
private static final Options options = new Options();
static {
options.addOption(Flag.URL.getShort(), Flag.URL.getLong(), true, "Ldap url (defaults to "+DEFAULT_URL+")");
options.addOption(Flag.USERNAME.getShort(), Flag.USERNAME.getLong(), true, "DN to bind with (defaults to \"\"");
options.addOption(Flag.PASSWORD.getShort(), Flag.PASSWORD.getLong(), true, "Password to bind with (defaults to \"\"");
options.addOption(Flag.OBJECTCLASS.getShort(), Flag.OBJECTCLASS.getLong(), true, "Comma separated list of object classes");
options.addOption(Flag.CLASS.getShort(), Flag.CLASS.getLong(), true, "Name of the Java class to create");
options.addOption(Flag.PACKAGE.getShort(), Flag.PACKAGE.getLong(), true, "Package to create the Java class in");
options.addOption(Flag.SYNTAX_MAP.getShort(), Flag.SYNTAX_MAP.getLong(), true, "Syntax map file (optional)");
options.addOption(Flag.OUTPUT_DIR.getShort(), Flag.OUTPUT_DIR.getLong(), true, "Base output directory (defaults to .)");
options.addOption(Flag.HELP.getShort(), Flag.HELP.getLong(), false, "Print this help message");
}
// Read list of LDAP syntaxes that are returned as byte[]
private static Set<String> readBinarySet(File binarySetFile)
throws IOException {
Set<String> result = new HashSet<String>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(binarySetFile));
String line;
while ((line = reader.readLine()) != null) {
String trimmed = line.trim();
if (trimmed.length() > 0) {
if (trimmed.charAt(0) != '#') {
String[] parts = trimmed.split("\\s");
if (parts.length > 0) {
result.add(parts[0]);
}
}
}
}
} finally {
if (reader != null) {
reader.close();
}
}
return result;
}
// Read mappings of LDAP syntaxes to Java classes.
private static Map<String, String> readSyntaxMap(File syntaxMapFile)
throws IOException {
Map<String, String> result = new HashMap<String, String>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(syntaxMapFile));
String line;
while ((line = reader.readLine()) != null) {
String trimmed = line.trim();
if (trimmed.length() > 0) {
if (trimmed.charAt(0) != '#') {
String[] parts = trimmed.split(",");
if (parts.length != 2) {
throw new IOException(String.format("Failed to parse line \"%1$s\"",
trimmed));
}
String partOne = parts[0].trim();
String partTwo = parts[1].trim();
if (partOne.length() == 0 || partTwo.length() == 0) {
throw new IOException(String.format("Failed to parse line \"%1$s\"",
trimmed));
}
result.put(partOne, partTwo);
}
}
}
} finally {
if (reader != null) {
reader.close();
}
}
return result;
}
// Bind to the directory, read and process the schema
private static ObjectSchema readSchema(String url, String user, String pass,
SyntaxToJavaClass syntaxToJavaClass, Set<String> binarySet, Set<String> objectClasses)
throws NamingException, ClassNotFoundException {
// Set up environment
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.PROVIDER_URL, url);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
if (user != null) {
env.put(Context.SECURITY_PRINCIPAL, user);
}
if (pass != null) {
env.put(Context.SECURITY_CREDENTIALS, pass);
}
DirContext context = new InitialDirContext(env);
DirContext schemaContext = context.getSchema("");
SchemaReader reader = new SchemaReader(schemaContext, syntaxToJavaClass, binarySet);
ObjectSchema schema = reader.getObjectSchema(objectClasses);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Schema - %1$s", schema.toString()));
}
return schema;
}
// Create the Java
private static void createCode(String packageName,
String className, ObjectSchema schema, Set<SyntaxToJavaClass.ClassInfo> imports, File outputFile)
throws IOException, TemplateException {
Configuration freeMarkerConfiguration = new Configuration();
freeMarkerConfiguration.setClassForTemplateLoading(loaderClass, "");
freeMarkerConfiguration.setObjectWrapper(new DefaultObjectWrapper());
// Build the model for FreeMarker
Map<String, Object> model = new HashMap<String, Object>();
model.put("package", packageName);
model.put("class", className);
model.put("schema", schema);
model.put("imports", imports);
// Have FreeMarker process the model with the template
Template template = freeMarkerConfiguration.getTemplate(TEMPLATE_FILE);
if (LOG.isDebugEnabled()) {
Writer out = new OutputStreamWriter(System.out);
template.process(model, out);
out.flush();
}
LOG.debug(String.format("Writing java to: %1$s", outputFile.getAbsolutePath()));
FileOutputStream outputStream=new FileOutputStream(outputFile);
Writer out = new OutputStreamWriter(outputStream);
template.process(model, out);
out.flush();
out.close();
}
// Create the output file for the generated code along with all intervening directories
private static File makeOutputFile(String outputDir, String packageName, String className)
throws IOException {
// Convert the package name to a path
Pattern pattern=Pattern.compile("\\.");
Matcher matcher=pattern.matcher(packageName);
String sepToUse=File.separator;
if (sepToUse.equals("\\")) {
sepToUse="\\\\";
}
// Try to create the necessary directories
String directoryPath=outputDir+File.separator+matcher.replaceAll(sepToUse);
File directory=new File(directoryPath);
File outputFile=new File(directory, className+".java");
LOG.debug(String.format("Attempting to create output file at %1$s", outputFile.getAbsolutePath()));
try {
directory.mkdirs();
outputFile.createNewFile();
} catch (SecurityException se) {
throw new IOException(String.format("Can't write to output file %1$s", outputFile.getAbsoluteFile()));
} catch (IOException ioe) {
throw new IOException(String.format("Can't write to output file %1$s", outputFile.getAbsoluteFile()));
}
return outputFile;
}
private static Set<String> parseObjectClassesFlag(String objectClassesFlag) {
Set<String> objectClasses = new HashSet<String>();
for (String objectClassFlag : objectClassesFlag.split(",")) {
if (objectClassFlag.length() > 0) {
objectClasses.add(objectClassFlag.toLowerCase().trim());
}
}
return objectClasses;
}
private static void error(String message) {
System.err.println(String.format("%1$s: %2$s", SchemaToJava.class.getSimpleName(), message));
System.exit(1);
}
public static void main(String[] argv) {
CommandLineParser parser = new PosixParser();
CommandLine cmd = null;
// Parse out the command line options
try {
cmd = parser.parse(options, argv);
} catch (ParseException e) {
error(e.toString());
}
// If the help flag is specified ignore other flags, print a usage message and exit
if (cmd.hasOption(Flag.HELP.getShort())) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(120, SchemaToJava.class.getSimpleName(), null, options, null, true);
System.exit(0);
}
// Class name flag
String className = cmd.getOptionValue(Flag.CLASS.getShort());
if (className == null) {
error("You must specify the name of a Java class to create");
}
// Package name flag
String packageName = cmd.getOptionValue(Flag.PACKAGE.getShort());
if (packageName == null) {
error("You must specifiy a package name");
}
// Output base directory
String outputDir = cmd.getOptionValue(Flag.OUTPUT_DIR.getShort(), ".");
File outputFile = null;
try {
outputFile = makeOutputFile(outputDir, packageName, className);
} catch (IOException e) {
error(e.toString());
}
// Get the flags we need to bind to the directory
String url = cmd.getOptionValue(Flag.URL.getShort(), DEFAULT_URL);
String user = cmd.getOptionValue(Flag.USERNAME.getShort());
String pass = cmd.getOptionValue(Flag.PASSWORD.getShort());
// Parse out object classes
String objectClassesFlag = cmd.getOptionValue(Flag.OBJECTCLASS.getShort());
if (objectClassesFlag==null) {
error("You must specificy a package name");
}
Set<String> objectClasses = parseObjectClassesFlag(objectClassesFlag);
if (objectClasses.size()==0) {
error("You must specificy a package name");
}
// Look for the optional syntax to Java class mapping file
String syntaxMapFileName = cmd.getOptionValue(Flag.SYNTAX_MAP.getShort(), null);
SyntaxToJavaClass syntaxToJavaClass=new SyntaxToJavaClass(new HashMap<String, String>());
if (syntaxMapFileName!=null) {
File syntaxMapFile=new File(syntaxMapFileName);
if (syntaxMapFile.canRead()) {
try {
syntaxToJavaClass = new SyntaxToJavaClass(readSyntaxMap(syntaxMapFile));
} catch (IOException e) {
error(String.format("Error reading syntax map file %1$s - %2$s",
syntaxMapFile.getAbsolutePath(), e.toString()));
}
} else {
error(String.format("Cannot read syntax map file %s$1",
syntaxMapFile.getAbsolutePath()));
}
}
// Read binary mapping file
URL binarySetUrl=loaderClass.getResource(BINARY_FILE);
if (binarySetUrl==null) {
error(String.format("Can't locatate binary mappings file %1$s", BINARY_FILE));
}
File binarySetFile=new File(binarySetUrl.getFile());
if (!binarySetFile.canRead()) {
error(String.format("Can't read from binary mappings file %1$s", BINARY_FILE));
}
Set<String> binarySet = null;
try {
binarySet = readBinarySet(binarySetFile);
} catch (IOException e) {
error(String.format("Error reading binary set file %1$s - %2$s", binarySetFile.getAbsolutePath(), e));
}
// Read schema from the directory
ObjectSchema schema=null;
try {
schema=readSchema(url, user, pass, syntaxToJavaClass, binarySet, objectClasses);
} catch (NamingException ne) {
error(String.format("Error processing schema - %1$s", ne));
} catch (ClassNotFoundException cnfe) {
error(String.format("Error processing schema - %1$s", cnfe));
}
// Work out what imports we need
Set<SyntaxToJavaClass.ClassInfo> imports = new HashSet<SyntaxToJavaClass.ClassInfo>();
for (AttributeSchema attributeSchema : schema.getMay()) {
SyntaxToJavaClass.ClassInfo classInfo = syntaxToJavaClass.getClassInfo(attributeSchema.getSyntax());
if (classInfo != null) {
String classPackageName = classInfo.getPackageName();
if (classPackageName != null && classPackageName.length() > 0) {
imports.add(classInfo);
}
}
}
// Create the Java code
try {
createCode(packageName, className, schema, imports, outputFile);
} catch (TemplateException te) {
error(String.format("Error generating code - %1$s", te.toString()));
} catch (IOException ioe) {
error(String.format("Error generatign code - %1$s", ioe.toString()));
}
}
}

View File

@@ -0,0 +1,233 @@
package org.springframework.ldap.odm.tools;
import java.io.PrintStream;
import java.util.Hashtable;
import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
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.InitialDirContext;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
/**
* A simple utility to list LDAP directory schema.
* <p>
* <code>SchemaViewer</code> takes the following flags:
* <ul>
* <li><code>-h,--help&lt;</code> Print this help message</li>
* <li><code>-l,--url &lt;arg></code> Ldap url of directory to bind to (defaults to ldap://127.0.0.1:389)</li>
* <li><code>-u,--username &lt;arg></code> DN to bind with (defaults to "")</li>
* <li><code>-p,--password &lt;arg></code> Password to bind with (defaults to "")</li>
* <li><code>-o,--objectclass &lt;arg></code> Object class name or ? for all. Print object class schema</li>
* <li><code>-a,--attribute &lt;arg></code> Attribute name or ? for all. Print attribute schema</li>
* <li><code>-s,--syntax &lt;arg></code> Syntax or ? for all. Print syntax</li>
* </ul>
*
* Only one of <code>-a</code>, <code>-o</code> and <code>-s</code> should be specified.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*
*/
public final class SchemaViewer {
private static final String DEFAULT_URL="ldap://127.0.0.1:389";
private enum Flag {
URL("l", "url"),
USERNAME("u", "username"),
PASSWORD("p", "password"),
OBJECTCLASS("o", "objectclass"),
ATTRIBUTE("a", "attribute"),
SYNTAX("s", "syntax"),
HELP("h", "help"),
ERROR("e", "error");
private String shortName;
private String longName;
private Flag(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
public String getShort() {
return shortName;
}
public String getLong() {
return longName;
}
@Override
public String toString() {
return String.format("short=%1$s, long=%2$s", shortName, longName);
}
}
private enum SchemaContext {
OBJECTCLASS("ClassDefinition"), ATTRIBUTE("AttributeDefinition"), SYNTAX("SyntaxDefinition");
private String value;
private SchemaContext(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return String.format("value=%1$s", value);
}
}
private static final Options options = new Options();
static {
options.addOption(Flag.URL.getShort(), Flag.URL.getLong(), true, "Ldap url (defaults to "+DEFAULT_URL+")");
options.addOption(Flag.USERNAME.getShort(), Flag.USERNAME.getLong(), true, "DN to bind with (defaults to \"\")");
options.addOption(Flag.PASSWORD.getShort(), Flag.PASSWORD.getLong(), true, "Password to bind with defaults to \"\")");
options.addOption(Flag.OBJECTCLASS.getShort(), Flag.OBJECTCLASS.getLong(), true,
"Object class name or ? for all. Print object class schema");
options.addOption(Flag.ATTRIBUTE.getShort(), Flag.ATTRIBUTE.getLong(), true,
"Attribute name or ? for all. Print attribute schema");
options.addOption(Flag.SYNTAX.getShort(), Flag.SYNTAX.getLong(), true,
"Syntax OID or ? for all. Print attribute syntax");
options.addOption(Flag.HELP.getShort(), Flag.HELP.getLong(), false, "Print this help message");
options.addOption(Flag.ERROR.getShort(), Flag.ERROR.getLong(), false, "Send output to standard error");
}
private static void printAttrs(Attributes attrs) throws NamingException {
NamingEnumeration<? extends Attribute> attrsEnum = attrs.getAll();
while (attrsEnum.hasMore()) {
Attribute currentAttr = attrsEnum.next();
outstream.print(String.format("%1$s:", currentAttr.getID()));
NamingEnumeration<?> valuesEnum = currentAttr.getAll();
while (valuesEnum.hasMoreElements()) {
outstream.print(String.format("%1$s ", valuesEnum.nextElement().toString()));
}
outstream.println();
}
}
private static void printObject(String contextName, String schemaName, DirContext schemaContext)
throws NameNotFoundException, NamingException {
DirContext oContext = (DirContext)schemaContext.lookup(contextName + "/" + schemaName);
outstream.println("NAME:" + schemaName);
printAttrs(oContext.getAttributes(""));
}
private static void printSchema(String contextName, DirContext schemaContext) throws NameNotFoundException,
NamingException {
outstream.println();
NamingEnumeration<NameClassPair> schemaList = schemaContext.list(contextName);
while (schemaList.hasMore()) {
NameClassPair ncp = schemaList.nextElement();
printObject(contextName, ncp.getName(), schemaContext);
outstream.println();
}
outstream.println();
}
private static void print(String optionValue, String contextName, DirContext schemaContext)
throws NameNotFoundException, NamingException {
if (optionValue.equals(WILDCARD)) {
printSchema(contextName, schemaContext);
} else {
printObject(contextName, optionValue, schemaContext);
}
}
private static PrintStream outstream=System.out;
private static String WILDCARD = "?";
public static void main(String[] argv) {
CommandLineParser parser = new PosixParser();
CommandLine cmd = null;
try {
cmd = parser.parse(options, argv);
} catch (ParseException e) {
System.out.println(e.getMessage());
System.exit(1);
}
if (cmd.hasOption(Flag.HELP.getShort())) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(120, SchemaViewer.class.getSimpleName(), null, options, null, true);
System.exit(0);
}
if (cmd.hasOption(Flag.ERROR.getShort())) {
outstream=System.err;
}
String url = cmd.getOptionValue(Flag.URL.getShort(), DEFAULT_URL);
String user = cmd.getOptionValue(Flag.USERNAME.getShort(), "");
String pass = cmd.getOptionValue(Flag.PASSWORD.getShort(), "");
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.PROVIDER_URL, url);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
if (user != null) {
env.put(Context.SECURITY_PRINCIPAL, user);
}
if (pass != null) {
env.put(Context.SECURITY_CREDENTIALS, pass);
if (user == null) {
System.err.println("You must specify a user if you specify a password");
System.exit(1);
}
}
try {
DirContext context = new InitialDirContext(env);
DirContext schemaContext = context.getSchema("");
if (cmd.hasOption(Flag.OBJECTCLASS.getShort())) {
print(cmd.getOptionValue(Flag.OBJECTCLASS.getShort()), SchemaContext.OBJECTCLASS.getValue(),
schemaContext);
}
if (cmd.hasOption(Flag.ATTRIBUTE.getShort())) {
print(cmd.getOptionValue(Flag.ATTRIBUTE.getShort()), SchemaContext.ATTRIBUTE.getValue(), schemaContext);
}
if (cmd.hasOption(Flag.SYNTAX.getShort())) {
print(cmd.getOptionValue(Flag.SYNTAX.getShort()), SchemaContext.SYNTAX.getValue(), schemaContext);
}
} catch (AuthenticationException e) {
System.err.println(String.format("Failed to bind to ldap server at %1$s", url));
} catch (CommunicationException e) {
System.err.println(String.format("Failed to contact ldap server at %1$s", url));
} catch (NameNotFoundException e) {
System.err.println(String.format("Can't find object %1$s", e.getMessage()));
} catch (NamingException e) {
System.err.println(e.toString());
}
}
}

View File

@@ -0,0 +1,63 @@
package org.springframework.ldap.odm.tools;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* A map from an LDAP syntax to the Java class used to represent it.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
/* package */ final class SyntaxToJavaClass {
public final static class ClassInfo {
private final String className;
private final String packageName;
private ClassInfo(String className, String packageName) {
this.className = className;
this.packageName = packageName;
}
public String getClassName() {
return className;
}
public String getPackageName() {
return packageName;
}
public String getFullClassName() {
StringBuilder result=new StringBuilder();
if (packageName!=null) {
result.append(packageName).append(".").append(className);
} else {
result.append(className);
}
return result.toString();
}
}
private final Map<String, ClassInfo> mapSyntaxToClassInfo = new HashMap<String, ClassInfo>();
public SyntaxToJavaClass(Map<String, String> mapSyntaxToClass) {
for (Entry<String, String> syntaxAndClass : mapSyntaxToClass.entrySet()) {
String fullClassName = syntaxAndClass.getValue().trim();
String packageName = null;
String className = null;
int lastDotIndex = fullClassName.lastIndexOf('.');
if (lastDotIndex != -1) {
className = fullClassName.substring(lastDotIndex + 1);
packageName = fullClassName.substring(0, lastDotIndex);
} else {
className = fullClassName;
}
mapSyntaxToClassInfo.put(syntaxAndClass.getKey(), new ClassInfo(className, packageName));
}
}
public ClassInfo getClassInfo(String syntax) {
return mapSyntaxToClassInfo.get(syntax);
}
}

View File

@@ -0,0 +1,8 @@
/**
* Provides a tool to create a Java class representation of a set of LDAP object classes
* and a simple tool to view LDAP schema.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.tools;

View File

@@ -0,0 +1,19 @@
package org.springframework.ldap.odm.typeconversion;
import org.springframework.ldap.NamingException;
/**
* Thrown by the conversion framework to indicate an error condition - typically a failed type conversion.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
@SuppressWarnings("serial")
public final class ConverterException extends NamingException {
public ConverterException(final String message) {
super(message);
}
public ConverterException(final String message, final Throwable e) {
super(message, e);
}
}

View File

@@ -0,0 +1,31 @@
package org.springframework.ldap.odm.typeconversion;
/**
* A simple interface to be implemented to provide type conversion functionality.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public interface ConverterManager {
/**
* Determine whether this converter manager is able to carry out a specified conversion.
*
* @param fromClass Convert from the <code>fromClass</code>.
* @param syntax Using the LDAP syntax (may be null).
* @param toClass To the <code>toClass</code>.
* @return <code>True</code> if the conversion is supported, <code>false</code> otherwise.
*/
boolean canConvert(Class<?> fromClass, String syntax, Class<?> toClass);
/**
* Convert a given source object with an optional LDAP syntax to an instance of a given class.
*
* @param <T> The class to convert to.
* @param source The object to convert.
* @param syntax The LDAP syntax to use (may be null).
* @param toClass The class to convert to.
* @return The converted object.
*
* @throws ConverterException If the conversion can not be successfully completed.
*/
<T> T convert(Object source, String syntax, Class<T> toClass);
}

View File

@@ -0,0 +1,19 @@
package org.springframework.ldap.odm.typeconversion.impl;
/**
* Interface specifying the conversion between two classes
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public interface Converter {
/**
* Attempt to convert a given object to a named class.
*
* @param <T> The class to convert to.
* @param source The object to convert.
* @param toClass The class to convert to.
* @return The converted class or null if the conversion was not possible.
* @throws Exception Any exception may be throw by a Converter on error.
*/
<T> T convert(Object source, Class<T> toClass) throws Exception;
}

View File

@@ -0,0 +1,193 @@
package org.springframework.ldap.odm.typeconversion.impl;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
/**
* A utility class to allow {@link ConverterManagerImpl} instances to be easily configured via <code>spring.xml</code>
* <p>
* The following shows a typical simple example which creates two {@link Converter} instances:
* <ul>
* <li><code>fromStringConverter</code></li>
* <li><code>toStringConverter</code></li>
* </ul>
* Configured in an {@link ConverterManagerImpl} to:
* <ul>
* <li>Use <code>fromStringConverter</code> to convert from <code>String</code> to <code>Byte, Short,
* Integer, Long, Float, Double, Boolean</code> </li>
* <li>Use <code>toStringConverter</code> to convert from <code>Byte, Short,
* Integer, Long, Float, Double, Boolean</code> to <code>String</code></li>
* </ul>
* <pre>
* &lt;bean id="converterManager" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
* &lt;property name="converterConfig">
* &lt;set>
* &lt;bean class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
* &lt;property name="fromClasses">
* &lt;set>
* &lt;value>java.lang.String&lt;/value>
* &lt;/set>
* &lt;/property>
* &lt;property name="toClasses">
* &lt;set>
* &lt;value>java.lang.Byte&lt;/value>
* &lt;value>java.lang.Short&lt;/value>
* &lt;value>java.lang.Integer&lt;/value>
* &lt;value>java.lang.Long&lt;/value>
* &lt;value>java.lang.Float&lt;/value>
* &lt;value>java.lang.Double&lt;/value>
* &lt;value>java.lang.Boolean&lt;/value>
* &lt;/set>
* &lt;/property>
* &lt;property name="converter" ref="fromStringConverter" />
* &lt;/bean>
* &lt;bean class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
* &lt;property name="fromClasses">
* &lt;set>
* &lt;value>java.lang.Byte&lt;/value>
* &lt;value>java.lang.Short&lt;/value>
* &lt;value>java.lang.Integer&lt;/value>
* &lt;value>java.lang.Long&lt;/value>
* &lt;value>java.lang.Float&lt;/value>
* &lt;value>java.lang.Double&lt;/value>
* &lt;value>java.lang.Boolean&lt;/value>
* &lt;/set>
* &lt;/property>
* &lt;property name="toClasses">
* &lt;set>
* &lt;value>java.lang.String&lt;/value>
* &lt;/set>
* &lt;/property>
* &lt;property name="converter" ref="toStringConverter" />
* &lt;/bean>
* &lt;/set>
* &lt;/property>
* &lt;/bean>
* </pre>
* {@link ConverterConfig} has a second constructor which takes an additional parameter to allow
* an LDAP syntax to be defined.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public final class ConverterManagerFactoryBean implements FactoryBean {
private static Log LOG = LogFactory.getLog(ConverterManagerFactoryBean.class);
/**
* Configuration information for a single Converter instance.
*/
public final static class ConverterConfig {
// The set of classes the Converter will convert from.
private Set<Class<?>> fromClasses = new HashSet<Class<?>>();
// The (optional) LDAP syntax.
private String syntax=null;
// The set of classes the Converter will convert to.
private Set<Class<?>> toClasses = new HashSet<Class<?>>();
// The Converter to use.
private Converter converter=null;
public ConverterConfig() {
}
/**
* @param fromClasses Comma separated list of classes the {@link Converter} should can convert from.
*/
public void setFromClasses(Set<Class<?>> fromClasses) {
this.fromClasses=fromClasses;
}
/**
* @param toClasses Comma separated list of classes the {@link Converter} can convert to.
*/
public void setToClasses(Set<Class<?>> toClasses) {
this.toClasses=toClasses;
}
/**
* @param syntax An LDAP syntax supported by the {@link Converter}.
*/
public void setSyntax(String syntax) {
this.syntax=syntax;
}
/**
* @param converter The {@link Converter} to use.
*/
public void setConverter(Converter converter) {
this.converter=converter;
}
@Override
public String toString() {
return String.format("fromClasses=%1$s, syntax=%2$s, toClasses=%3$s, converter=%4$s",
fromClasses, syntax, toClasses, converter);
}
}
private Set<ConverterConfig> converterConfigList=null;
/**
* @param converterConfigList
*/
public void setConverterConfig(Set<ConverterConfig> converterConfigList) {
this.converterConfigList=converterConfigList;
}
/**
* Creates a ConverterManagerImpl populating it with Converter instances from the converterConfigList property.
*
* @return The newly created {@link org.springframework.ldap.odm.typeconversion.ConverterManager}.
* @throws ClassNotFoundException Thrown if any of the classes to be converted to or from cannot be found.
*
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public Object getObject() throws Exception {
if (converterConfigList==null) {
throw new FactoryBeanNotInitializedException("converterConfigList has not been set");
}
ConverterManagerImpl result = new ConverterManagerImpl();
for (ConverterConfig converterConfig : converterConfigList) {
if (converterConfig.fromClasses==null ||
converterConfig.toClasses==null ||
converterConfig.converter==null) {
throw new FactoryBeanNotInitializedException(
String.format("All of fromClasses, toClasses and converter must be specified in bean %1$s",
converterConfig.toString()));
}
for (Class<?> fromClass : converterConfig.fromClasses) {
for (Class<?> toClass : converterConfig.toClasses) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Adding converter from %1$s to %2$s", fromClass, toClass));
}
result.addConverter(fromClass, converterConfig.syntax, toClass, converterConfig.converter);
}
}
}
return result;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<?> getObjectType() {
return ConverterManagerImpl.class;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,161 @@
package org.springframework.ldap.odm.typeconversion.impl;
import java.util.HashMap;
import java.util.Map;
import org.springframework.ldap.odm.typeconversion.ConverterException;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
/**
* An implementation of {@link org.springframework.ldap.odm.typeconversion.ConverterManager}.
* <p>
* The algorithm used is to:
* <ol>
* <li>Try to find and use a {@link Converter} registered for the
* <code>fromClass</code>, <code>syntax</code> and <code>toClass</code> and use it.</li>
* <li>If this fails, then if the <code>toClass isAssignableFrom</code>
* the <code>fromClass</code> then just assign it.</li>
* <li>If this fails try to find and use a {@link Converter} registered for the <code>fromClass</code> and
* the <code>toClass</code> ignoring the <code>syntax</code>.</li>
* <li>If this fails then throw a {@link org.springframework.ldap.odm.typeconversion.ConverterException}.</li>
* </ol>
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public final class ConverterManagerImpl implements ConverterManager {
/**
* Separator used to form keys into the converters Map.
*/
private static final String KEY_SEP = ":";
/**
* Map of keys created via makeConverterKey to Converter instances.
*/
private final Map<String, Converter> converters = new HashMap<String, Converter>();
/**
* Make a key into the converters map - the keys is formed from the <code>fromClass</code>, syntax and <code>toClass</code>
*
* @param fromClass The class to convert from.
* @param syntax The LDAP syntax.
* @param toClass The class to convert to.
* @return key
*/
private String makeConverterKey(Class<?> fromClass, String syntax, Class<?> toClass) {
StringBuilder key = new StringBuilder();
if (syntax==null) {
syntax="";
}
key.append(fromClass.getName()).append(KEY_SEP).append(syntax).append(KEY_SEP).append(toClass.getName());
return key.toString();
}
/**
* Create an empty ConverterManagerImpl
*/
public ConverterManagerImpl() {
}
/**
* Used to help in the process of dealing with primitive types by mapping them to
* their equivalent boxed class.
*/
private static Map<Class<?>, Class<?>> primitiveTypeMap = new HashMap<Class<?>, Class<?>>();
static {
primitiveTypeMap.put(Byte.TYPE, Byte.class);
primitiveTypeMap.put(Short.TYPE, Short.class);
primitiveTypeMap.put(Integer.TYPE, Integer.class);
primitiveTypeMap.put(Long.TYPE, Long.class);
primitiveTypeMap.put(Float.TYPE, Float.class);
primitiveTypeMap.put(Double.TYPE, Double.class);
primitiveTypeMap.put(Boolean.TYPE, Boolean.class);
primitiveTypeMap.put(Character.TYPE, Character.class);
}
/*
* (non-Javadoc)
* @see org.springframework.ldap.odm.typeconversion.ConverterManager#canConvert(java.lang.Class, java.lang.String, java.lang.Class)
*/
public boolean canConvert(Class<?> fromClass, String syntax, Class<?> toClass) {
Class<?> fixedToClass = toClass;
if (toClass.isPrimitive()) {
fixedToClass = primitiveTypeMap.get(toClass);
}
Class<?> fixedFromClass = fromClass;
if (fromClass.isPrimitive()) {
fixedFromClass = primitiveTypeMap.get(fromClass);
}
return fixedToClass.isAssignableFrom(fixedFromClass) ||
(converters.get(makeConverterKey(fixedFromClass, syntax, fixedToClass)) != null) ||
(converters.get(makeConverterKey(fixedFromClass, null, fixedToClass)) != null);
}
/*
* (non-Javadoc)
* @see org.springframework.ldap.odm.typeconversion.ConverterManager#convert(java.lang.Object, java.lang.String, java.lang.Class)
*/
@SuppressWarnings("unchecked")
public <T> T convert(Object source, String syntax, Class<T> toClass) {
Object result = null;
// What are we converting form
Class<?> fromClass = source.getClass();
// Deal with primitives
Class<?> targetClass = toClass;
if (toClass.isPrimitive()) {
targetClass = primitiveTypeMap.get(toClass);
}
// Try to convert with any syntax we have been given
Converter syntaxConverter = converters.get(makeConverterKey(fromClass, syntax, targetClass));
if (syntaxConverter != null) {
try {
result = syntaxConverter.convert(source, targetClass);
} catch (Exception e) {
// Ignore as we may still be able to convert successfully
}
}
// Do we actually need to do any conversion?
if (result == null && targetClass.isAssignableFrom(fromClass)) {
result = source;
}
// If we were given a syntax and we failed to convert drop back to any mapping
// that will work from class -> to class
if (result == null && syntax != null) {
Converter nullSyntaxConverter = converters.get(makeConverterKey(fromClass, null, targetClass));
if (nullSyntaxConverter != null) {
try {
result = nullSyntaxConverter.convert(source, targetClass);
} catch (Exception e) {
// Handled at the end of the method
}
}
}
if (result == null) {
throw new ConverterException(String.format(
"Cannot convert %1$s of class %2$s via syntax %3$s to class %4$s", source, source.getClass(),
syntax, toClass));
}
// We cannot do the safe thing of doing a .cast as we need to rely on auto-unboxing to deal with primitives!
return (T)result;
}
/**
* Add a {@link Converter} to this <code>ConverterManager</code>.
*
* @param fromClass The class the <code>Converter</code> should be used to convert from.
* @param syntax The LDAP syntax that the <code>Converter</code> should be used for.
* @param toClass The class the <code>Converter</code> should be used to convert to.
* @param converter The <code>Converter</code> to add.
*/
public void addConverter(Class<?> fromClass, String syntax, Class<?> toClass, Converter converter) {
converters.put(makeConverterKey(fromClass, syntax, toClass), converter);
}
}

View File

@@ -0,0 +1,24 @@
package org.springframework.ldap.odm.typeconversion.impl.converters;
import java.lang.reflect.Constructor;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
/**
* A Converter from a {@link java.lang.String} to any class which has a single argument
* public constructor taking a {@link java.lang.String}.
* <p>
* This should only be used as a fall-back converter, as a last attempt.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public final class FromStringConverter implements Converter {
/* (non-Javadoc)
* @see org.springframework.ldap.odm.typeconversion.impl.Converter#convert(java.lang.Object, java.lang.Class)
*/
public <T> T convert(Object source, Class<T> toClass) throws Exception {
Constructor<T> constructor = toClass.getConstructor(java.lang.String.class);
return constructor.newInstance(source);
}
}

View File

@@ -0,0 +1,21 @@
package org.springframework.ldap.odm.typeconversion.impl.converters;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
/**
* A Converter from any class to a {@link java.lang.String} via the <code>toString</code> method.
* <p>
* This should only be used as a fall-back converter, as a last attempt.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public final class ToStringConverter implements Converter {
/* (non-Javadoc)
* @see org.springframework.ldap.odm.typeconversion.impl.Converter#convert(java.lang.Object, java.lang.Class)
*/
public <T> T convert(Object source, Class<T> toClass) {
return toClass.cast(source.toString());
}
}

View File

@@ -0,0 +1,7 @@
/**
* Provides some basic implementations of the {@link org.springframework.ldap.odm.typeconversion.impl.Converter} interface.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.typeconversion.impl.converters;

View File

@@ -0,0 +1,8 @@
/**
* Provides an implementation of the {@link org.springframework.ldap.odm.typeconversion.ConverterManager} interface.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.typeconversion.impl;

View File

@@ -0,0 +1,10 @@
/**
* Provides an interface to be implemented to create a type conversion framework.
* <p>
* This is used to convert between the LDAP and Java representations of attributes.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.typeconversion;

View File

@@ -0,0 +1,19 @@
log4j.rootLogger=error, stdout
log4j.logger.org.springframework.ldap.odm=error
log4j.logger.org.springframework=error
# Messages to the console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
# Message to a log file
log4j.appender.log=org.apache.log4j.RollingFileAppender
log4j.appender.log.File=jndi.log
log4j.appender.log.MaxFileSize=100KB
log4j.appender.log.MaxBackupIndex=1
log4j.appender.log.layout=org.apache.log4j.PatternLayout
log4j.appender.log.layout.ConversionPattern=%p %t %c - %m%n

View File

@@ -0,0 +1,16 @@
# List of syntaxes that will be returned by the JNDI provider as byte[], taken from
# http://java.sun.com/products/jndi/tutorial/ldap/misc/attrs.html
# See all http://www.ietf.org/rfc/rfc2256.txt for attribute names with ;binary suffix
# Syntax Used by Attributes
# ------ ------------------
1.3.6.1.4.1.1466.115.121.1.23 photo, personalSignature
1.3.6.1.4.1.1466.115.121.1.4 audio
1.3.6.1.4.1.1466.115.121.1.28 jpegPhoto
1.3.6.1.4.1.1466.115.121.1.40 javaSerializedData
1.3.6.1.4.1.1466.115.121.1.40 userPassword
1.3.6.1.4.1.1466.115.121.1.8 userCertificate, cACertificate
1.3.6.1.4.1.1466.115.121.1.9 authorityRevocationList, certificateRevocationList
1.3.6.1.4.1.1466.115.121.1.10 crossCertificatePair
1.3.6.1.4.1.1466.115.121.1.6 x500UniqueIdentifier

View File

@@ -0,0 +1,163 @@
<#ftl strip_whitespace="true">
<#macro commaSeparatedList strings>
<#local first=true/>
<#list strings as listValue>
<#if !first>
, <#t/>
<#else>
<#local first=false/>
</#if>
"${listValue}"<#t/>
</#list>
</#macro>
<#macro doAttribute attributes>
<#list attributes as attribute>
<#local binary=attribute.isBinary?string(", type=Type.BINARY", "")>
<#lt/> @Attribute(name="${attribute.name}", syntax="${attribute.syntax}"${binary})
<#if attribute.isMultiValued>
<#lt/> private List<${attribute.scalarType}> ${attribute.name}=new ArrayList<${attribute.scalarType}>();
<#else>
<#lt/> private ${attribute.scalarType} ${attribute.name};
</#if>
</#list>
</#macro>
<#macro getSet attributes>
<#list attributes as attribute>
<#if attribute.name!="objectClass">
<#if attribute.isMultiValued>
<#lt/> public void add${attribute.name?cap_first}(${attribute.scalarType} ${attribute.name}) {
<#lt/> this.${attribute.name}.add(${attribute.name});
<#lt/> }
<#lt/> public void remove${attribute.name?cap_first}(${attribute.scalarType} ${attribute.name}) {
<#lt/> this.${attribute.name}.remove(${attribute.name});
<#lt/> }
<#lt/> public Iterator<${attribute.scalarType}> get${attribute.name?cap_first}Iterator() {
<#lt/> return ${attribute.name}.iterator();
<#lt/> }
<#else>
<#lt/> public ${attribute.scalarType} get${attribute.name?cap_first}() {
<#lt/> return ${attribute.name};
<#lt/> }
<#lt/> public void set${attribute.name?cap_first}(${attribute.scalarType} ${attribute.name}) {
<#lt/> this.${attribute.name}=${attribute.name};
<#lt/> }
</#if>
<#else>
<#lt/> public Iterator<String> get${attribute.name?cap_first}Iterator() {
<#lt/> return Collections.unmodifiableList(${attribute.name}).iterator();
<#lt/> }
</#if>
</#list>
</#macro>
<#macro equalsCode attributes>
<#list attributes as attribute>
<#lt/> append(${attribute.name}, other.${attribute.name}).
</#list>
</#macro>
<#macro hashCode attributes>
<#list attributes as attribute>
<#lt/> append(${attribute.name}).
</#list>
</#macro>
package ${package};
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import javax.naming.Name;
<#list imports as import>
import ${import.packageName}.${import.className};
</#list>
import static org.springframework.ldap.odm.annotations.Attribute.*;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
/**
* Generated by Spring LDAP ODM to represent the LDAP object classes:
* <ul>
<#list schema.objectClass as objectClass>
* <li>${objectClass}</li>
</#list>
* </ul>
*/
@Entry(objectClasses={<@commaSeparatedList schema.objectClass/>})
public final class ${class} {
@Id
private Name dn;
<@doAttribute schema.must/>
<@doAttribute schema.may/>
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn=dn;
}
<@getSet schema.must/>
<@getSet schema.may/>
<#lt/> @Override
<#lt/> public String toString() {
<#lt/>
<#lt/> return new ToStringBuilder(this).
<#lt/> append("dn", dn).
<#list schema.must as attribute>
<#lt/> append("${attribute.name}", ${attribute.name}).
</#list>
<#list schema.may as attribute>
<#lt/> append("${attribute.name}", ${attribute.name}).
</#list>
<#lt/> toString();
<#lt/> }
<#lt/> @Override
<#lt/> public int hashCode() {
<#lt> return new HashCodeBuilder().
<@hashCode schema.must/>
<@hashCode schema.may/>
<#lt/> toHashCode();
<#lt/> }
<#lt/> @Override
<#lt/> public boolean equals(Object obj) {
<#lt/> if (this == obj)
<#lt/> return true;
<#lt/> if (obj == null)
<#lt/> return false;
<#lt/> if (getClass() != obj.getClass())
<#lt/> return false;
<#lt/>
<#lt/> ${class} other = (${class}) obj;
<#lt/>
<#lt/> return new EqualsBuilder().
<#lt/> append(dn, other.dn).
<@equalsCode schema.must/>
<@equalsCode schema.may/>
<#lt/> isEquals();
<#lt/> }
}

View File

@@ -0,0 +1,137 @@
package org.springframework.ldap.odm.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
/**
* Automatically generated to represent the LDAP object classes
* "organizationalunit", "top".
*/
@Entry(objectClasses = { "organizationalunit", "top" })
public final class OrganizationalUnit {
@Id
private Name dn;
@Attribute(name = "objectClass", syntax = "1.3.6.1.4.1.1466.115.121.1.38")
private List<String> objectClass = new ArrayList<String>();
@Attribute(name = "ou", syntax = "1.3.6.1.4.1.1466.115.121.1.15")
private String ou;
@Attribute(name = "street", syntax = "1.3.6.1.4.1.1466.115.121.1.15")
private String street;
@Attribute(name = "description", syntax = "1.3.6.1.4.1.1466.115.121.1.15")
private String description;
public OrganizationalUnit() {
}
public OrganizationalUnit(Name dn, String street, String description) {
this.dn = dn;
this.street = street;
this.description = description;
objectClass.add("top");
objectClass.add("organizationalUnit");
int size = dn.size();
if (size > 1) {
ou = dn.get(size - 1).split("=")[1];
} else {
ou = "";
}
}
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn = dn;
}
public List<String> getObjectClasses() {
return Collections.unmodifiableList(objectClass);
}
public String getOu() {
return ou;
}
public String getStreet() {
return street;
}
public String getDescription() {
return description;
}
@Override
public String toString() {
return String.format("objectClasses=%1$s | dn=%2$s | ou=%3$s | street=%4$s | description=%5$s", objectClass,
dn, ou, street, description);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((dn == null) ? 0 : dn.hashCode());
result = prime * result + ((objectClass == null) ? 0 : new HashSet<String>(objectClass).hashCode());
result = prime * result + ((ou == null) ? 0 : ou.hashCode());
result = prime * result + ((street == null) ? 0 : street.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OrganizationalUnit other = (OrganizationalUnit) obj;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (dn == null) {
if (other.dn != null)
return false;
} else if (!dn.equals(other.dn))
return false;
if (objectClass == null) {
if (other.objectClass != null)
return false;
} else
if (objectClass.size()!=other.objectClass.size() ||
!(new HashSet<String>(objectClass)).equals(new HashSet<String>(other.objectClass)))
return false;
if (ou == null) {
if (other.ou != null)
return false;
} else if (!ou.equals(other.ou))
return false;
if (street == null) {
if (other.street != null)
return false;
} else if (!street.equals(other.street))
return false;
return true;
}
}

View File

@@ -0,0 +1,201 @@
package org.springframework.ldap.odm.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import org.springframework.ldap.odm.annotations.Transient;
import org.springframework.ldap.odm.annotations.Attribute.Type;
// Simple LDAP entry for testing
@Entry(objectClasses = { "inetorgperson", "organizationalperson", "person", "top" })
public final class Person {
public Person() {
}
public Person(Name dn, String surname, List<String> desc, int telephoneNumber, byte[] jpegPhoto) {
this.dn = dn;
this.surname = surname;
this.desc = desc;
this.telephoneNumber = telephoneNumber;
this.jpegPhoto = jpegPhoto;
objectClasses = new ArrayList<String>();
objectClasses.add("top");
objectClasses.add("person");
objectClasses.add("organizationalPerson");
objectClasses.add("inetOrgPerson");
int size = dn.size();
if (size > 1) {
cn = dn.get(size - 1).split("=")[1];
} else {
cn = "";
}
}
@Transient
private String someRandomField = null;
@Transient
private List<String> someRandomList = new ArrayList<String>();
@Attribute(name = "objectClass")
private List<String> objectClasses;
@Id
private Name dn;
// No annotation on purpose!
private String cn;
@Attribute(name = "sn")
private String surname;
// Everything should be sets and in search operations also as results can be in any order
@Attribute(name = "description")
private List<String> desc;
@Attribute
private int telephoneNumber;
@Attribute(type = Type.BINARY)
byte[] jpegPhoto;
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn = dn;
}
public String getCn() {
return cn;
}
public void setCn(String cn) {
this.cn = cn;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public List<String> getDesc() {
return desc;
}
public void setDesc(List<String> desc) {
this.desc = desc;
}
public int getTelephoneNumber() {
return telephoneNumber;
}
public void setTelephoneNumber(int telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
public byte[] getJpegPhoto() {
return jpegPhoto;
}
public void setJpegPhoto(byte[] jpegPhoto) {
this.jpegPhoto = jpegPhoto;
}
public List<String> getObjectClasses() {
return objectClasses;
}
@Override
public String toString() {
StringBuilder jpegString=new StringBuilder();
if (jpegPhoto!=null) {
for (byte b:jpegPhoto) {
jpegString.append(Byte.toString(b));
}
}
return String.format(
"objectClasses=%1$s | dn=%2$s | cn=%3$s | sn=%4$s | desc=%5$s | telephoneNumber=%6$s | jpegPhoto=%7$s",
objectClasses, dn, cn, surname, desc, telephoneNumber, jpegString);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cn == null) ? 0 : cn.hashCode());
result = prime * result + ((desc == null) ? 0 : new HashSet<String>(desc).hashCode());
result = prime * result + ((dn == null) ? 0 : dn.hashCode());
result = prime * result + Arrays.hashCode(jpegPhoto);
result = prime * result + ((objectClasses == null) ? 0 : new HashSet<String>(objectClasses).hashCode());
result = prime * result + ((someRandomField == null) ? 0 : someRandomField.hashCode());
result = prime * result + ((someRandomList == null) ? 0 : someRandomList.hashCode());
result = prime * result + ((surname == null) ? 0 : surname.hashCode());
result = prime * result + telephoneNumber;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (cn == null) {
if (other.cn != null)
return false;
} else if (!cn.equals(other.cn))
return false;
if (desc == null) {
if (other.desc != null)
return false;
} else if (desc.size()!=other.desc.size() || !(new HashSet<String>(desc)).equals(new HashSet<String>(other.desc)))
return false;
if (dn == null) {
if (other.dn != null)
return false;
} else if (!dn.equals(other.dn))
return false;
if (!Arrays.equals(jpegPhoto, other.jpegPhoto))
return false;
if (objectClasses == null) {
if (other.objectClasses != null)
return false;
} else if (desc.size()!=other.desc.size() || !(new HashSet<String>(objectClasses)).equals(new HashSet<String>(other.objectClasses)))
return false;
if (someRandomField == null) {
if (other.someRandomField != null)
return false;
} else if (!someRandomField.equals(other.someRandomField))
return false;
if (someRandomList == null) {
if (other.someRandomList != null)
return false;
} else if (!someRandomList.equals(other.someRandomList))
return false;
if (surname == null) {
if (other.surname != null)
return false;
} else if (!surname.equals(other.surname))
return false;
if (telephoneNumber != other.telephoneNumber)
return false;
return true;
}
}

View File

@@ -0,0 +1,207 @@
package org.springframework.ldap.odm.test;
import static org.junit.Assert.assertEquals;
import java.net.URI;
import java.util.BitSet;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ldap.odm.test.utils.ExecuteRunnable;
import org.springframework.ldap.odm.test.utils.RunnableTest;
import org.springframework.ldap.odm.typeconversion.ConverterException;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
import org.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl;
import org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter;
import org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter;
public final class TestConverterManager {
private ConverterManagerImpl converterManager;
@Before
public void setUp() {
converterManager = new ConverterManagerImpl();
Converter ptc = new FromStringConverter();
converterManager.addConverter(String.class, "", Byte.class, ptc);
converterManager.addConverter(String.class, "", Short.class, ptc);
converterManager.addConverter(String.class, "", Integer.class, ptc);
converterManager.addConverter(String.class, "", Long.class, ptc);
converterManager.addConverter(String.class, "", Double.class, ptc);
converterManager.addConverter(String.class, "", Float.class, ptc);
converterManager.addConverter(String.class, "", Boolean.class, ptc);
Converter tsc = new ToStringConverter();
converterManager.addConverter(Byte.class, "", String.class, tsc);
converterManager.addConverter(Short.class, "", String.class, tsc);
converterManager.addConverter(Integer.class, "", String.class, tsc);
converterManager.addConverter(Long.class, "", String.class, tsc);
converterManager.addConverter(Double.class, "", String.class, tsc);
converterManager.addConverter(Float.class, "", String.class, tsc);
converterManager.addConverter(Boolean.class, "", String.class, tsc);
Converter uric = new UriConverter();
converterManager.addConverter(URI.class, "", String.class, uric);
converterManager.addConverter(String.class, "", URI.class, uric);
}
@After
public void tearDown() {
converterManager = null;
}
private static class ConverterTestData<T> {
public final Class<T> destClass;
public final Object sourceData;
public final T expectedValue;
public final String syntax;
public ConverterTestData(Object sourceData, Class<T> destClass, T expectedValue) {
this(sourceData, "", destClass, expectedValue);
}
public ConverterTestData(Object sourceData, String syntax, Class<T> destClass, T expectedValue) {
this.destClass = destClass;
this.sourceData = sourceData;
this.expectedValue = expectedValue;
this.syntax = syntax;
}
@Override
public String toString() {
return String.format("sourceData=%1$s | syntax=%2$s | destClass=%3$s | expectedValue=%4$s", sourceData,
syntax, destClass, expectedValue);
}
}
// Class to Class conversion without any syntaxes
@Test
public void basicTypeConverion() throws Exception {
final ConverterTestData<?>[] primitiveTypeTests = new ConverterTestData<?>[] {
new ConverterTestData<Byte>("33", Byte.class, Byte.valueOf((byte)33)),
new ConverterTestData<Byte>("-88", Byte.class, Byte.valueOf((byte)-88)),
new ConverterTestData<Short>("666", Short.class, Short.valueOf((short)666)),
new ConverterTestData<Short>("-123", Short.class, Short.valueOf((short)-123)),
new ConverterTestData<Integer>("123", Integer.class, Integer.valueOf(123)),
new ConverterTestData<Integer>("-500", Integer.class, Integer.valueOf(-500)),
new ConverterTestData<Long>("123456", Long.class, Long.valueOf(123456)),
new ConverterTestData<Long>("-654321", Long.class, Long.valueOf(-654321)),
new ConverterTestData<Double>("2", Double.class, Double.valueOf(2)),
new ConverterTestData<Double>("-0.4", Double.class, Double.valueOf(-0.4)),
new ConverterTestData<Float>("666", Float.class, Float.valueOf(666)),
new ConverterTestData<Float>("-0.75", Float.class, Float.valueOf(-0.75F)),
new ConverterTestData<Boolean>("false", Boolean.class, Boolean.FALSE),
new ConverterTestData<Boolean>("TRUE", Boolean.class, Boolean.TRUE),
new ConverterTestData<String>("This is a string", String.class, "This is a string"),
new ConverterTestData<String>("This is another String", String.class, "This is another String"),
new ConverterTestData<String>((byte)66, String.class, "66"),
new ConverterTestData<String>((int)1234, String.class, "1234"),
new ConverterTestData<String>((int)-9876, String.class, "-9876"),
new ConverterTestData<URI>("http://google.com/", URI.class, new URI("http://google.com/")),
new ConverterTestData<URI>("http://apache.org/index.html", URI.class, new URI(
"http://apache.org/index.html")),
new ConverterTestData<String>(new URI("http://google.com/"), String.class, "http://google.com/"),
new ConverterTestData<String>(new URI("http://apache.org/index.html"), String.class,
"http://apache.org/index.html") };
new ExecuteRunnable<ConverterTestData<?>>().runTests(new RunnableTest<ConverterTestData<?>>() {
public void runTest(ConverterTestData<?> testData) {
assertEquals(testData.expectedValue, converterManager.convert(testData.sourceData, "",
testData.destClass));
}
}, primitiveTypeTests);
}
private static class SquaredConverter implements Converter {
public <T> T convert(Object source, Class<T> toClass) throws Exception {
Integer intSource = null;
if (source.getClass() == String.class) {
intSource = new Integer((String)source);
}
else {
if (source.getClass() == Integer.class) {
intSource = (Integer)source;
}
}
Integer result = null;
if (intSource != null) {
result = intSource * intSource;
}
return toClass.cast(result);
}
}
private static class CubedConverter implements Converter {
public <T> T convert(Object source, Class<T> toClass) throws Exception {
Integer intSource = null;
if (source.getClass() == String.class) {
intSource = new Integer((String)source);
}
else {
if (source.getClass() == Integer.class) {
intSource = (Integer)source;
}
}
Integer result = null;
if (intSource != null) {
result = intSource * intSource * intSource;
}
return toClass.cast(result);
}
}
// Tests using syntaxes for "finer grained" mapping
@Test
public void syntaxBasedConversion() throws Exception {
Converter squaredConverter = new SquaredConverter();
converterManager.addConverter(String.class, "1", Integer.class, squaredConverter);
converterManager.addConverter(Integer.class, "1", Integer.class, squaredConverter);
Converter cubedConverter = new CubedConverter();
converterManager.addConverter(String.class, "2", Integer.class, cubedConverter);
converterManager.addConverter(Integer.class, "3", Integer.class, cubedConverter);
final ConverterTestData<?>[] syntaxTests = new ConverterTestData<?>[] {
new ConverterTestData<Integer>("3", "", Integer.class, Integer.valueOf(3)),
new ConverterTestData<Integer>("4", "", Integer.class, Integer.valueOf(4)),
new ConverterTestData<Integer>(5, "", Integer.class, Integer.valueOf(5)),
new ConverterTestData<Integer>(6, "", Integer.class, Integer.valueOf(6)),
new ConverterTestData<Integer>("3", "1", Integer.class, Integer.valueOf(9)),
new ConverterTestData<Integer>("4", "1", Integer.class, Integer.valueOf(16)),
new ConverterTestData<Integer>(5, "1", Integer.class, Integer.valueOf(25)),
new ConverterTestData<Integer>(6, "1", Integer.class, Integer.valueOf(36)),
new ConverterTestData<Integer>("3", "2", Integer.class, Integer.valueOf(27)),
new ConverterTestData<Integer>("4", "2", Integer.class, Integer.valueOf(64)),
new ConverterTestData<Integer>(5, "3", Integer.class, Integer.valueOf(125)),
new ConverterTestData<Integer>(6, "3", Integer.class, Integer.valueOf(216)), };
new ExecuteRunnable<ConverterTestData<?>>().runTests(new RunnableTest<ConverterTestData<?>>() {
public void runTest(ConverterTestData<?> testData) {
assertEquals(testData.expectedValue, converterManager.convert(testData.sourceData, testData.syntax,
testData.destClass));
}
}, syntaxTests);
}
// No converter for classes
@Test(expected = ConverterException.class)
public void noClassConverter() throws Exception {
converterManager.convert(BitSet.class, "", Integer.class);
}
// Invalid syntax so converter fails
@Test(expected = ConverterException.class)
public void invalidSyntax() throws Exception {
converterManager.convert(String.class, "not a uri", URI.class);
}
}

View File

@@ -0,0 +1,622 @@
package org.springframework.ldap.odm.test;
import static org.junit.Assert.assertEquals;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.naming.Name;
import javax.naming.directory.SearchControls;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import org.springframework.ldap.odm.core.OdmException;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.ldap.odm.core.impl.InvalidEntryException;
import org.springframework.ldap.odm.core.impl.MetaDataException;
import org.springframework.ldap.odm.core.impl.OdmManagerImpl;
import org.springframework.ldap.odm.core.impl.UnmanagedClassException;
import org.springframework.ldap.odm.test.utils.ExecuteRunnable;
import org.springframework.ldap.odm.test.utils.GetFreePort;
import org.springframework.ldap.odm.test.utils.RunnableTest;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
import org.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl;
import org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter;
import org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter;
import org.springframework.ldap.test.LdapTestUtils;
// Tests all OdmManager functions
public final class TestLdap {
private static final Log LOG = LogFactory.getLog(TestLdap.class);
// Base DN for test data
private static final DistinguishedName baseName = new DistinguishedName("o=Whoniverse");
// This port MUST be free on local host for these unit tests to function.
private static int port;
// Maximum number of objects to return in testing
private static final long COUNT_LIMIT=20;
// Maximum time to wait for results in testing (ms)
private static final int TIME_LIMIT=60000;
private static SearchControls searchControls=
new SearchControls(SearchControls.SUBTREE_SCOPE,
COUNT_LIMIT,
TIME_LIMIT,
null,
true,
false);
private ConverterManagerImpl converterManager;
private ContextSource contextSource;
private OdmManager odmManager;
// Base 64 encoded jpeg photo used to test binary reading and writing
private static byte[] photo;
static {
try {
String photoString="/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkS"+
"Ew8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRg"+
"yIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wA"+
"ARCAAnABoDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAA"+
"gEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY"+
"GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipK"+
"TlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8v"+
"P09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFB"+
"AQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygp"+
"KjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJm"+
"aoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9"+
"oADAMBAAIRAxEAPwDx2z0mK6gV/tSo2SCpUHGD9Qa2vDvgGfX7+eI3ot4EOEmEefMOOcZPQdOvW"+
"s3VLGzsWiihUvM0ayONxATPIHXk4wfxFdd4L1aw0rw3eS3V3GHWRswkgugwCpVSckEk9OhB9azu"+
"2ro6lCKnyyZwmvaHcaBrV1plxKkkluw+dRkMCAQfbgjiqi27sob1GfuVc1q9n1XVL3UdqxJI/wD"+
"q/MXIXGAMd+B2HWoUZ1jUbZeABwKqzMJWT0Ld3cfaJ5ZFYtnCg+oUBf6V2XhvwVpcvhxfFXibVF"+
"tNK3lEgjJ3yMGK7SRzkkHhRnHORXJQ6exVXB2r1K9Cfx7V0UWpeD7S3WJ9N1TUPLYssV7cBYkZg"+
"AxAU4zwOcZ4HoKtppWQ4tSk5SNPx14o8M3/AITXT/D2kRRxB1jE4RIyuOc7ME84PzcHr2PPmX2y"+
"YcZX8q37fQxPpqzsH8xvuqAeeuO/0H3e9Zn2dxwbK5JHXCH/AOJqNOhdSM1ZzVjRNwRbjGRxxzn"+
"FdRouijV9Pi1C6toEMqn7MsK7SqBiCxOSSSVI5J4x6miiliW1FMzpbszreUw3H2VcuscpGT3UE9"+
"Rn0HStE+IrZSQbEuRxvU4De4yc0UVFKCnuerms2vZryP/Z";
byte[] photoBytes=photoString.getBytes("US-ASCII");
photo=Base64.decodeBase64(photoBytes);
} catch (IOException e) {
throw new RuntimeException("Problem decoding photo", e);
}
}
@BeforeClass
public static void setUpClass() throws Exception {
// Added because the close down of Apache DS on Linux does
// not seem to free up its port.
port=GetFreePort.getFreePort();
// Start an LDAP server and import test data
LdapTestUtils.startApacheDirectoryServer(port, baseName.toString(), "odm-test", "", "", null);
}
@AfterClass
public static void tearDownClass() throws Exception {
LdapTestUtils.destroyApacheDirectoryServer("", "");
}
private static ContextSource getContextSource(String url, String username, String password) throws Exception {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(url);
contextSource.setUserDn(username);
contextSource.setPassword(password);
contextSource.setPooled(false);
contextSource.afterPropertiesSet();
return contextSource;
}
// Pulled out of setup method to allow it to be called with parameters from main as
// an integration test
public void setUp(String url, String username, String password) throws Exception {
// Create some basic converters and a converter manager
converterManager = new ConverterManagerImpl();
Converter ptc = new FromStringConverter();
converterManager.addConverter(String.class, "", Byte.class, ptc);
converterManager.addConverter(String.class, "", Short.class, ptc);
converterManager.addConverter(String.class, "", Integer.class, ptc);
converterManager.addConverter(String.class, "", Long.class, ptc);
converterManager.addConverter(String.class, "", Double.class, ptc);
converterManager.addConverter(String.class, "", Float.class, ptc);
converterManager.addConverter(String.class, "", Boolean.class, ptc);
Converter tsc = new ToStringConverter();
converterManager.addConverter(Byte.class, "", String.class, tsc);
converterManager.addConverter(Short.class, "", String.class, tsc);
converterManager.addConverter(Integer.class, "", String.class, tsc);
converterManager.addConverter(Long.class, "", String.class, tsc);
converterManager.addConverter(Double.class, "", String.class, tsc);
converterManager.addConverter(Float.class, "", String.class, tsc);
converterManager.addConverter(Boolean.class, "", String.class, tsc);
// Bind to the directory
contextSource = getContextSource(url, username, password);
// Clear out any old data - and load the test data
LdapTestUtils.cleanAndSetup(contextSource, baseName, new ClassPathResource("testdata.ldif"));
// Create our OdmManager
Set<Class<?>> managedClasses=new HashSet<Class<?>>();
managedClasses.add(Person.class);
managedClasses.add(OrganizationalUnit.class);
odmManager = new OdmManagerImpl(converterManager, contextSource, managedClasses);
}
@Before
public void setUp() throws Exception {
setUp("ldap://127.0.0.1:" + port, "", "");
}
@After
public void tearDown() throws Exception {
LdapTestUtils.clearSubContexts(contextSource, baseName);
odmManager=null;
contextSource=null;
converterManager=null;
}
private enum PersonName {
WILLIAM(0), PATRICK(1), JON(2), TOM(3), PETER(4), DAVROS(5), DALEKS(6), MASTER(7);
private int index;
private PersonName(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
private Person[] personTestData=new Person[] {
new Person(new DistinguishedName("cn=William Hartnell,ou=Doctors,o=Whoniverse"), "Hartnell", Arrays
.asList(new String[] { "First Doctor", "Grumpy" }), 1, null),
new Person(new DistinguishedName("cn=Patrick Troughton,ou=Doctors,o=Whoniverse"), "Troughton", Arrays
.asList(new String[] { "Second Doctor", "Clown" }), 2, null),
new Person(new DistinguishedName("cn=Jon Pertwee,ou=Doctors,o=Whoniverse"), "Pertwee", Arrays
.asList(new String[] { "Third Doctor", "Dandy" }), 3, null),
new Person(new DistinguishedName("cn=Tom Baker,ou=Doctors,o=Whoniverse"), "Baker", Arrays
.asList(new String[] { "Fourth Doctor", "The one and only!" }), 4, null),
new Person(new DistinguishedName("cn=Peter Davison,ou=Doctors,o=Whoniverse"), "Davison", Arrays
.asList(new String[] { "Fifth Doctor" }), 5, null),
new Person(new DistinguishedName("cn=Davros,ou=Enemies,o=Whoniverse"), "Unknown", Arrays
.asList(new String[] { "Creator of the Daleks", "Kaled head scientist" }), 0, null),
new Person(new DistinguishedName("cn=Daleks,ou=Enemies,o=Whoniverse"), "NA", Arrays
.asList(new String[] { "The Doctor's greatest foe" }), 0, null),
new Person(new DistinguishedName("cn=Master,ou=Enemies,o=Whoniverse"), "Unknown", Arrays
.asList(new String[] { "An evil Time Lord" }), 0, photo), };
// Read various entries from the sample data set and check they are what we'd expect.
@Test
public void read() throws Exception {
new ExecuteRunnable<Person>().runTests(new RunnableTest<Person>() {
public void runTest(Person testData) {
Name dn = testData.getDn();
LOG.debug(String.format("reading - %1$s", dn));
Person personEntry = odmManager.read(Person.class, dn);
LOG.debug(String.format("read - %1$s", personEntry));
assertEquals(testData, personEntry);
}
}, personTestData);
}
private static class SearchTestData {
private String search;
private SearchControls searchScope;
private Person[] people;
public SearchTestData(String search, SearchControls searchScope, Person[] people) {
this.search = search;
this.searchScope = searchScope;
this.people = people;
}
}
private SearchTestData[] searchTestData = {
new SearchTestData("(sn=Unknown)",
searchControls,
new Person[] {
personTestData[PersonName.DAVROS.getIndex()],
personTestData[PersonName.MASTER.getIndex()] }),
new SearchTestData("(description=*Doctor)",
searchControls,
new Person[] {
personTestData[PersonName.WILLIAM.getIndex()],
personTestData[PersonName.PATRICK.getIndex()],
personTestData[PersonName.JON.getIndex()],
personTestData[PersonName.TOM.getIndex()],
personTestData[PersonName.PETER.getIndex()] }),
};
// Carry out various searches against the test data set and check the results are what we'd expect.
@Test
public void search() throws Exception {
new ExecuteRunnable<SearchTestData>().runTests(new RunnableTest<SearchTestData>() {
public void runTest(SearchTestData testData) {
String search = testData.search;
LOG.debug(String.format("searching - %1$s", search));
List<Person> results = odmManager.search(Person.class, baseName, testData.search, testData.searchScope);
LOG.debug(String.format("found - %1$s", results));
assertEquals(new HashSet<Person>(Arrays.asList(testData.people)), new HashSet<Person>(results));
}
}, searchTestData);
}
private enum OrganizationalName {
ENEMIES(0), ASSISTANTS(1), DOCTORS(2);
private int index;
private OrganizationalName(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
private static OrganizationalUnit ouTestData[]=new OrganizationalUnit[] {
new OrganizationalUnit(new DistinguishedName("ou=Enemies,o=Whoniverse"), "Acacia Avenue", "The bad guys"),
new OrganizationalUnit(new DistinguishedName("ou=Assistants,o=Whoniverse"), "Somewhere in space", "The plucky helpers"),
new OrganizationalUnit(new DistinguishedName("ou=Doctors,o=Whoniverse"), "Somewhere in time", "Our hero"),
};
// Check everything works OK with a second managed class
@Test
public void testSecondOc() {
LOG.debug("Reading all organizatinalUnits");
List<OrganizationalUnit> allOus=odmManager.findAll(OrganizationalUnit.class, baseName, searchControls);
LOG.debug(String.format("Found - %1$s", allOus));
assertEquals(new HashSet<OrganizationalUnit>(Arrays.asList(ouTestData)), new HashSet<OrganizationalUnit>(allOus));
OrganizationalUnit testOu=ouTestData[OrganizationalName.ASSISTANTS.getIndex()];
LOG.debug(String.format("Reading - %1$s", testOu.getDn()));
OrganizationalUnit ou=odmManager.read(OrganizationalUnit.class, testOu.getDn());
LOG.debug(String.format("Found - %1$s", ou));
assertEquals(testOu, ou);
}
// Find all entries managed by the OdmManager in the test data set and check they are what we expect.
@Test
public void findAll() throws Exception {
LOG.debug("finding all people");
List<Person> allPeople = odmManager.findAll(Person.class, baseName, searchControls);
LOG.debug(String.format("found %1$s", allPeople));
assertEquals(new HashSet<Person>(Arrays.asList(personTestData)), new HashSet<Person>(allPeople));
}
private Person[] createTestData = {
new Person(new DistinguishedName("cn=Colin Baker,ou=Doctors,o=Whoniverse"), "Baker", Arrays
.asList(new String[] { "Sixth Doctor" }), 6, null),
new Person(new DistinguishedName("cn=Sylvester McCoy,ou=Doctors,o=Whoniverse"), "McCoy", Arrays
.asList(new String[] { "Seventh Doctor" }), 7, null),
new Person(new DistinguishedName("cn=Paul McGann,ou=Doctors,o=Whoniverse"), "McGann", Arrays
.asList(new String[] { "Eigth Doctor" }), 8, photo), };
// Create some entries, read them back and check they are what we'd expect.
@Test
public void create() throws Exception {
for (Person person : createTestData) {
LOG.debug(String.format("creating - %1$s", person));
odmManager.create(person);
}
LOG.debug("Created all, reading back");
new ExecuteRunnable<Person>().runTests(new RunnableTest<Person>() {
public void runTest(Person testData) {
Name dn = testData.getDn();
LOG.debug(String.format("reading - %1$s", dn));
Person personEntry = odmManager.read(Person.class, dn);
LOG.debug(String.format("read - %1$s", personEntry));
assertEquals(testData, personEntry);
}
}, createTestData);
}
// Update an entry from the test data set, read it back and check it is what we'd expect.
@Test
public void update() throws Exception {
Person william = personTestData[PersonName.WILLIAM.getIndex()];
william.setTelephoneNumber(666);
william.setSurname("Harvey");
odmManager.update(william);
Person readWilliam = odmManager.read(Person.class, william.getDn());
assertEquals(william, readWilliam);
}
private Person[] deleteData = {
personTestData[PersonName.JON.getIndex()],
personTestData[PersonName.TOM.getIndex()], personTestData[PersonName.DAVROS.getIndex()], };
private Person[] whatsLeft = {
personTestData[PersonName.WILLIAM.getIndex()],
personTestData[PersonName.PATRICK.getIndex()], personTestData[PersonName.PETER.getIndex()],
personTestData[PersonName.DALEKS.getIndex()], personTestData[PersonName.MASTER.getIndex()], };
// Delete a some entries from the the test data set and check what's left is what we'd expect
@Test
public void delete() throws Exception {
for (Person toDelete : deleteData) {
LOG.debug(String.format("deleting - %1$s", toDelete.getDn()));
odmManager.delete(toDelete);
}
List<Person> allPeople = odmManager.findAll(Person.class, baseName, searchControls);
assertEquals(new HashSet<Person>(Arrays.asList(whatsLeft)), new HashSet<Person>(allPeople));
}
// Trying to read a non-existant entry should be flagged as an error
@Test(expected = NameNotFoundException.class)
public void readNonExistant() throws Exception {
odmManager.read(Person.class, new DistinguishedName("cn=Hili Harvey,ou=Doctors,o=Whoniverse"));
}
// Read an entry with classes in addition to those supported by the Entry
@Test(expected = OdmException.class)
public void readAdditionalObjectClasses() throws Exception {
odmManager.read(Person.class, new DistinguishedName("cn=Paul Harvey,ou=Doctors,o=Whoniverse"));
}
private final static class NoEntry {
@SuppressWarnings("unused")
@Id
Name id;
}
// Every class to be managed must be annotated @Entry
@Test(expected = MetaDataException.class)
public void noEntryAnnotation() {
((OdmManagerImpl)odmManager).addManagedClass(NoEntry.class);
}
@Entry(objectClasses="test")
private final static class NoId {
}
// There must be a field with the @Id annotation
@Test(expected = MetaDataException.class)
public void noId() {
((OdmManagerImpl)odmManager).addManagedClass(NoId.class);
}
@Entry(objectClasses="test")
private final static class TwoIds {
@SuppressWarnings("unused")
@Id
private Name firstId;
@SuppressWarnings("unused")
@Id
private Name secondId;
@SuppressWarnings("unused")
public TwoIds() {
}
}
// Only one field may be annotated @Id
@Test(expected = MetaDataException.class)
public void twoIds() {
((OdmManagerImpl)odmManager).addManagedClass(TwoIds.class);
}
@Entry(objectClasses="test")
public final static class NoConstructor {
@SuppressWarnings("unused")
@Id
private Name id;
public NoConstructor(String aValue) {
}
}
// All Entry annotated classes must have a zero argument public constructor
@Test(expected = InvalidEntryException.class)
public void noConstructor() {
((OdmManagerImpl)odmManager).addManagedClass(NoConstructor.class);
}
@Entry(objectClasses="test")
public final static class AttributeOnId {
@SuppressWarnings("unused")
@Id
@Attribute
private Name id;
}
// It is illegal put put both the Id and the Attribute annotation on the same field
@Test(expected = MetaDataException.class)
public void attributeOnId() {
((OdmManagerImpl)odmManager).addManagedClass(AttributeOnId.class);
}
@Entry(objectClasses="test")
public final static class IdIsNotAName {
@SuppressWarnings("unused")
@Id
private String id;
}
// The field annotation with @Id must be of type javax.naming.Name
@Test(expected = MetaDataException.class)
public void idIsNotAName() {
((OdmManagerImpl)odmManager).addManagedClass(IdIsNotAName.class);
}
@Entry(objectClasses="test")
public final static class MissingConverter {
@SuppressWarnings("unused")
@Id
private Name id;
@SuppressWarnings("unused")
private BufferedImage image;
}
// The OdmManager should flag any missing converters when it is instantiated
@Test(expected = InvalidEntryException.class)
public void missingConverter() {
((OdmManagerImpl)odmManager).addManagedClass(MissingConverter.class);
}
@Entry(objectClasses="test")
public final static class WrongClassForOc {
@SuppressWarnings("unused")
@Id
private Name id;
@SuppressWarnings("unused")
@Attribute(name="objectClass")
private int ocs;
}
// The OdmManager should flag if the objectClass attribute is not of the appropriate type
@Test(expected = MetaDataException.class)
public void wrongClassForOc() {
((OdmManagerImpl)odmManager).addManagedClass(WrongClassForOc.class);
}
// The OdmManager should flag any attempt to use a "unmanaged" class
@Test(expected = UnmanagedClassException.class)
public void unManagedClass() {
((OdmManagerImpl)odmManager).read(Integer.class, baseName);
}
private enum Flag {
URL("l", "url"),
USERNAME("u", "username"),
PASSWORD("p", "password"),
HELP("h", "help");
private String shortName;
private String longName;
private Flag(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
public String getShort() {
return shortName;
}
public String getLong() {
return longName;
}
@Override
public String toString() {
return String.format("short=%1$s, long=%2$s", shortName, longName);
}
}
private static final String DEFAULT_LDAP_URL="ldap://localhost:389";
private static final String DEFAULT_USERNAME="";
private static final String DEFAULT_PASSWORD="";
private static final Options options = new Options();
static {
options.addOption(Flag.URL.getShort(), Flag.URL.getLong(), true, "Ldap url to bind to, defaults to "+DEFAULT_LDAP_URL);
options.addOption(Flag.USERNAME.getShort(), Flag.USERNAME.getLong(), true, "DN to bind with, defaults to "+DEFAULT_USERNAME);
options.addOption(Flag.PASSWORD.getShort(), Flag.PASSWORD.getLong(), true, "Password to bind with defaults to "+DEFAULT_PASSWORD);
options.addOption(Flag.HELP.getShort(), Flag.HELP.getLong(), false, "Print this help message");
}
private static void runLdapTestCases(String url, String username, String password, String[] testCases) throws Exception {
for (String testCase:testCases) {
LOG.debug(String.format("Starting ldap test case %1$s", testCase));
// Set up
TestLdap testLdap=new TestLdap();
testLdap.setUp(url, username, password);
// Run the test
Method testMethod=testLdap.getClass().getMethod(testCase);
testMethod.invoke(testLdap);
// Tear down
testLdap.tearDown();
LOG.debug(String.format("Test case %1$s completed", testCase));
}
}
/*
* Run unit tests as an integration test against an external LDAP server.
*
* Three flags are required:
*
* -l ldap url of target server
* -u dn to bind with
* -p password to bind with
*
* The organisation o=Whoniverse must already exists and the bound user must have
* write permission.
*
*/
public static void main(String[] argv) throws Exception {
CommandLineParser parser = new PosixParser();
CommandLine cmd = null;
try {
cmd = parser.parse(options, argv);
} catch (ParseException e) {
System.out.println(e.getMessage());
System.exit(1);
}
if (cmd.hasOption(Flag.HELP.getShort())) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(120, TestLdap.class.getSimpleName(), null, options, null, true);
System.exit(0);
}
String url=cmd.getOptionValue(Flag.URL.getShort(), DEFAULT_LDAP_URL);
String username=cmd.getOptionValue(Flag.USERNAME.getShort(), DEFAULT_USERNAME);
String password=cmd.getOptionValue(Flag.PASSWORD.getShort(), DEFAULT_PASSWORD);
// Run all the tests
runLdapTestCases(url, username, password, new String[] { "create", "delete", "findAll", "read", "search", "update", "testSecondOc" } );
}
}

View File

@@ -0,0 +1,87 @@
package org.springframework.ldap.odm.test;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.springframework.ldap.odm.test.utils.ExecuteRunnable;
import org.springframework.ldap.odm.test.utils.RunnableTest;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
import org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean;
public class TestManagerConverterFactory {
private static class NullConverter implements Converter {
public <T> T convert(Object source, Class<T> toClass) throws Exception {
return null;
}
}
private static final Converter nullConverter=new NullConverter();
private static class ConverterConfigTestData {
private Class<?>[] fromClasses;
private String syntax;
private Class<?>[] toClasses;
private ConverterConfigTestData(Class<?>[] fromClasses, String syntax, Class<?>[] toClasses) {
this.fromClasses=fromClasses;
this.syntax=syntax;
this.toClasses=toClasses;
}
}
private static ConverterConfigTestData[] converterConfigTestData=new ConverterConfigTestData[] {
new ConverterConfigTestData(new Class<?>[] { String.class }, "", new Class<?>[] { Integer.class }),
new ConverterConfigTestData(new Class<?>[] { Byte.class, java.lang.Integer.class }, "", new Class<?>[] { String.class, Long.class }),
new ConverterConfigTestData(new Class<?>[] { String.class }, "123", new Class<?>[] { java.net.URI.class }),
};
private static class ConverterTestData {
private final Class<?> fromClass;
private final String syntax;
private final Class<?> toClass;
private final boolean canConvert;
private ConverterTestData(Class<?> fromClass, String syntax, Class<?> toClass, boolean canConvert) {
this.fromClass=fromClass;
this.syntax=syntax;
this.toClass=toClass;
this.canConvert=canConvert;
}
}
private ConverterTestData[] converterTestData=new ConverterTestData[] {
new ConverterTestData(java.lang.String.class, "", java.lang.Integer.class, true),
new ConverterTestData(java.lang.Byte.class, "", java.lang.Long.class, true),
new ConverterTestData(java.lang.Integer.class, "444", java.lang.String.class, true),
new ConverterTestData(java.lang.String.class, "123", java.net.URI.class, true),
new ConverterTestData(java.lang.String.class, "123", java.lang.Byte.class, false),
new ConverterTestData(java.lang.Byte.class, "", java.lang.Integer.class, false)
};
@Test
public void testConverterFactory() throws Exception {
ConverterManagerFactoryBean converterManagerFactory=new ConverterManagerFactoryBean();
Set<ConverterManagerFactoryBean.ConverterConfig> configList=new HashSet<ConverterManagerFactoryBean.ConverterConfig>();
for (ConverterConfigTestData config:converterConfigTestData) {
ConverterManagerFactoryBean.ConverterConfig converterConfig=new ConverterManagerFactoryBean.ConverterConfig();
converterConfig.setFromClasses(new HashSet<Class<?>>(Arrays.asList(config.fromClasses)));
converterConfig.setSyntax(config.syntax);
converterConfig.setToClasses(new HashSet<Class<?>>(Arrays.asList(config.toClasses)));
converterConfig.setConverter(nullConverter);
configList.add(converterConfig);
}
converterManagerFactory.setConverterConfig(configList);
final ConverterManager converterManager=(ConverterManager)converterManagerFactory.getObject();
new ExecuteRunnable<ConverterTestData>().runTests(new RunnableTest<ConverterTestData>() {
public void runTest(ConverterTestData testData) {
assertEquals(testData.canConvert,
converterManager.canConvert(testData.fromClass, testData.syntax, testData.toClass));
}
}, converterTestData);
}
}

View File

@@ -0,0 +1,204 @@
package org.springframework.ldap.odm.test;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.odm.core.impl.OdmManagerImpl;
import org.springframework.ldap.odm.test.utils.CompilerInterface;
import org.springframework.ldap.odm.test.utils.GetFreePort;
import org.springframework.ldap.odm.tools.SchemaToJava;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
import org.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl;
import org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter;
import org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter;
import org.springframework.ldap.test.LdapTestUtils;
// Tests the generation of entry Java classes from LDAP schema
public final class TestSchemaToJava {
private static final Log LOG = LogFactory.getLog(TestLdap.class);
private static final DistinguishedName baseName = new DistinguishedName("o=Whoniverse");
private static final String tempDir=System.getProperty("java.io.tmpdir");
// These unit tests require this port to free on localhost
private static int port;
private ConverterManagerImpl converterManager;
private LdapContextSource contextSource;
@BeforeClass
public static void setUpClass() throws Exception {
// Added because the close down of Apache DS on Linux does
// not seem to free up its port.
port=GetFreePort.getFreePort();
// Start an in process LDAP server
LdapTestUtils.startApacheDirectoryServer(port, baseName.toString(), "odm-test", "", "", null);
}
@AfterClass
public static void tearDownClass() throws Exception {
// Stop the in process LDAP server
LdapTestUtils.destroyApacheDirectoryServer("", "");
}
@Before
public void setUp() throws Exception {
// Create some basic converters and a converter manager
converterManager = new ConverterManagerImpl();
Converter ptc = new FromStringConverter();
converterManager.addConverter(String.class, "", Byte.class, ptc);
converterManager.addConverter(String.class, "", Short.class, ptc);
converterManager.addConverter(String.class, "", Integer.class, ptc);
converterManager.addConverter(String.class, "", Long.class, ptc);
converterManager.addConverter(String.class, "", Double.class, ptc);
converterManager.addConverter(String.class, "", Float.class, ptc);
converterManager.addConverter(String.class, "", Boolean.class, ptc);
Converter tsc = new ToStringConverter();
converterManager.addConverter(Byte.class, "", String.class, tsc);
converterManager.addConverter(Short.class, "", String.class, tsc);
converterManager.addConverter(Integer.class, "", String.class, tsc);
converterManager.addConverter(Long.class, "", String.class, tsc);
converterManager.addConverter(Double.class, "", String.class, tsc);
converterManager.addConverter(Float.class, "", String.class, tsc);
converterManager.addConverter(Boolean.class, "", String.class, tsc);
// Bind to the directory
contextSource = new LdapContextSource();
contextSource.setUrl("ldap://127.0.0.1:" + port);
contextSource.setUserDn("");
contextSource.setPassword("");
contextSource.setPooled(false);
contextSource.afterPropertiesSet();
// Clear out any old data - and load the test data
LdapTestUtils.cleanAndSetup(contextSource, baseName, new ClassPathResource("testdata.ldif"));
}
@After
public void tearDown() throws Exception {
LdapTestUtils.destroyApacheDirectoryServer("", "");
contextSource=null;
converterManager=null;
}
// Figure out the path of the created Java file
private static String calculateOutputDirectory(String outputDir, String packageName) {
// Convert the package name to a path
Pattern pattern=Pattern.compile("\\.");
Matcher matcher=pattern.matcher(packageName);
String sepToUse=File.separator;
if (sepToUse.equals("\\")) {
sepToUse="\\\\";
}
return outputDir+File.separator+matcher.replaceAll(sepToUse);
}
// Due of the nature of the code under test this unit test is a little unusual:
//
// 1) Generate an entry class corresponding to objects classes
// "inetorgperson, organizationalperson, person, top"
// using the SchemaToJavaTool
// 2) Compile the generated code
// 3) Create an OdmManager to managing the newly created
// entry class.
// 4) Use this OdmManager to read an entry from LDAP and check the results.
//
@Test
@Ignore
public void generate() throws Exception {
final String className="Person";
final String packageName="org.springframework.ldap.odm.testclasses";
// Add classes dir to class path - needed for compilation
System.setProperty("java.class.path",
System.getProperty("java.class.path")+File.pathSeparator+"target/classes");
String[] flags=new String[] {
"--url", "ldap://127.0.0.1:"+port,
"--objectclasses", "inetorgperson",
"--syntaxmap", "target/test-classes/syntax-to-class-map.txt",
"--class", className,
"--package", packageName,
"--outputdir", tempDir };
// Generate the code using SchemaToJava
SchemaToJava.main(flags);
// Java 5 - we'll use the Java 6 Compiler API once we can drop support for Java 5.
String javaDir = calculateOutputDirectory(tempDir, packageName);
CompilerInterface.compile(javaDir, className+".java");
// Java 5
// OK it compiles so lets load our new class
URL[] urls = new URL[] { new File(tempDir).toURI().toURL() };
URLClassLoader ucl = new URLClassLoader(urls, getClass().getClassLoader());
Class<?> clazz = ucl.loadClass(packageName+"."+className);
// Create our OdmManager using our new class
OdmManagerImpl odmManager = new OdmManagerImpl(converterManager, contextSource);
odmManager.addManagedClass(clazz);
// And try reading from the directory using it
DistinguishedName testDn=new DistinguishedName(baseName);
testDn.addAll(new DistinguishedName("cn=William Hartnell,ou=Doctors"));
Object fromDirectory=odmManager.read(clazz, testDn);
LOG.debug(String.format("Read - %1$s", fromDirectory));
// Check some returned values
Method getDnMethod=clazz.getMethod("getDn");
Object dn=getDnMethod.invoke(fromDirectory);
assertEquals(testDn, dn);
Method getCnIteratorMethod=clazz.getMethod("getCnIterator");
@SuppressWarnings("unchecked")
Iterator<String> cnIterator=(Iterator<String>)getCnIteratorMethod.invoke(fromDirectory);
int cnCount=0;
while (cnIterator.hasNext()) {
cnCount++;
assertEquals("William Hartnell", cnIterator.next());
}
assertEquals(1, cnCount);
Method telephoneNumberIteratorMethod=clazz.getMethod("getTelephoneNumberIterator");
@SuppressWarnings("unchecked")
Iterator<Integer> telephoneNumberIterator=(Iterator<Integer>)telephoneNumberIteratorMethod.invoke(fromDirectory);
int telephoneNumberCount=0;
while (telephoneNumberIterator.hasNext()) {
telephoneNumberCount++;
assertEquals(Integer.valueOf(1), telephoneNumberIterator.next());
}
assertEquals(1, telephoneNumberCount);
// Reread and check whether equals and hashCode are at least sane
Object fromDirectory2=odmManager.read(clazz, testDn);
assertEquals(fromDirectory, fromDirectory2);
assertEquals(fromDirectory.hashCode(), fromDirectory2.hashCode());
}
}

View File

@@ -0,0 +1,125 @@
package org.springframework.ldap.odm.test;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.odm.test.utils.ExecuteRunnable;
import org.springframework.ldap.odm.test.utils.GetFreePort;
import org.springframework.ldap.odm.test.utils.RunnableTest;
import org.springframework.ldap.odm.tools.SchemaViewer;
import org.springframework.ldap.test.LdapTestUtils;
public final class TestSchemaViewer {
// Base DN for test data
private static final DistinguishedName baseName = new DistinguishedName("o=Whoniverse");
private static final String PRINCIPAL="";
private static final String CREDENTIALS="";
private static final String lineSeparator = System.getProperty ("line.separator");
private static int port;
private static String[] commonFlags;
@BeforeClass
public static void setUpClass() throws Exception {
// Added because the close down of Apache DS on Linux does
// not seem to free up its port.
port=GetFreePort.getFreePort();
commonFlags=new String[] {
"--url", "ldap://127.0.0.1:"+port,
"--username", "",
"--password", "",
"--error"};
// Start an in process LDAP server
LdapTestUtils.startApacheDirectoryServer(port, baseName.toString(), "odm-test", "", "", null);
}
@AfterClass
public static void tearDownClass() throws Exception {
LdapTestUtils.destroyApacheDirectoryServer(PRINCIPAL, CREDENTIALS);
}
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
private static String runSchemaViewer(String... flags) {
String result=null;
PrintStream originalOut=System.out;
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
System.setErr(new PrintStream(output));
List<String> commandFlags=
new ArrayList<String>(Arrays.asList(commonFlags));
commandFlags.addAll(Arrays.asList(flags));
SchemaViewer.main(commandFlags.toArray(new String[0]));
// Turn end of lines into | for portability
result=output.toString().trim().replace(lineSeparator, "|");
} finally {
System.setErr(originalOut);
}
return result;
}
private static class TestData {
private final String flag;
private final String value;
private final String result;
public TestData(String flag, String value, String result) {
this.flag=flag;
this.value=value;
this.result=result;
}
}
// This makes the test dependent on the order in which the data is returned - it is invalid to assume that this will not change
private static TestData[] viewerTestData=new TestData[] {
new TestData("-o", "top",
"NAME:top|MUST:objectClass |NAME:top |NUMERICOID:2.5.6.0 |DESC:top of the superclass chain |ABSTRACT:true"),
new TestData("-o", "country",
"NAME:country|MUST:c |SUP:top |NAME:country |STRUCTURAL:true |NUMERICOID:2.5.6.2 |DESC:RFC2256: a country |MAY:searchGuide description"),
new TestData("-a", "sn",
"NAME:sn|SUP:name |SYNTAX:1.3.6.1.4.1.1466.115.121.1.15 |NAME:sn surname |EQUALITY:caseIgnoreMatch |SUBSTR:caseIgnoreSubstringsMatch |USAGE:userApplications |NUMERICOID:2.5.4.4 |DESC:RFC2256: last (family) name(s) for which the entity is known by"),
new TestData("-a", "jpegPhoto",
"NAME:jpegPhoto|SYNTAX:1.3.6.1.4.1.1466.115.121.1.28 |NAME:jpegPhoto |USAGE:userApplications |NUMERICOID:0.9.2342.19200300.100.1.60 |DESC:RFC2798: a JPEG image"),
new TestData("-s", "jpeg",
"NAME:jpeg|NAME:JPEG |NUMERICOID:1.3.6.1.4.1.1466.115.121.1.28"),
new TestData("-s", "OID",
"NAME:OID|NAME:OID |NUMERICOID:1.3.6.1.4.1.1466.115.121.1.38"),
};
// Very simple test - mainly just to exercise the code and to
// ensure we get representative test coverage
@Test
public void testSchemaViewer() throws Exception {
new ExecuteRunnable<TestData>().runTests(new RunnableTest<TestData>() {
public void runTest(TestData testData) {
String result=runSchemaViewer(testData.flag, testData.value);
assertEquals(testData.result, result);
}
}, viewerTestData);
}
}

View File

@@ -0,0 +1,27 @@
package org.springframework.ldap.odm.test;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import jdepend.framework.JDepend;
import org.junit.Before;
import org.junit.Test;
public class TestsWithJdepend {
private JDepend jdepend;
@Before
public void setUp() throws IOException {
jdepend = new JDepend();
jdepend.addDirectory("target/classes");
}
@Test
public void testAllPackages() {
jdepend.analyze();
assertEquals(false, jdepend.containsCycles());
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.ldap.odm.test;
import java.net.URI;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
/**
* A bi-directional converter between {@link java.net.URI} and {@link java.lang.String}.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
public class UriConverter implements Converter {
/* (non-Javadoc)
* @see org.springframework.ldap.odm.typeconversion.impl.Converter#convert(java.lang.Object, java.lang.Class)
*/
public <T> T convert(Object source, Class<T> toClass) throws Exception {
T result = null;
if (String.class.isAssignableFrom(source.getClass()) && toClass == URI.class) {
result = toClass.cast(new URI((String)source));
} else {
if (URI.class.isAssignableFrom(source.getClass()) && toClass == String.class) {
result = toClass.cast(source.toString());
}
}
return result;
}
}

View File

@@ -0,0 +1,35 @@
package org.springframework.ldap.odm.test.utils;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
public class CompilerInterface {
// Compile the given file - when we can drop Java 5 we'll use the Java 6 compiler API
public static void compile(String directory, String file) throws Exception {
ProcessBuilder pb = new ProcessBuilder(
new String[] { "javac",
"-cp", "."+File.pathSeparatorChar+"target"+File.separatorChar+"classes"+
File.pathSeparatorChar+System.getProperty("java.class.path"),
directory+File.separatorChar+file });
pb.redirectErrorStream(true);
Process proc = pb.start();
InputStream is = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
char[] buf = new char[1024];
int count;
StringBuilder builder = new StringBuilder();
while ((count = isr.read(buf)) > 0) {
builder.append(buf, 0, count);
}
boolean ok = proc.waitFor() == 0;
if (!ok) {
throw new RuntimeException(builder.toString());
}
}
}

View File

@@ -0,0 +1,19 @@
package org.springframework.ldap.odm.test.utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// Simple utility class to run a given test over a set of test data
public final class ExecuteRunnable<U> {
public void runTests(RunnableTest<U> runnableTest, U[] testData) throws Exception {
StackTraceElement ste = Thread.currentThread().getStackTrace()[2];
Log LOG = LogFactory.getLog(ste.getClassName());
for (U testDatum : testData) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Running test with data %1$s", testDatum));
}
runnableTest.runTest(testDatum);
}
}
}

View File

@@ -0,0 +1,24 @@
package org.springframework.ldap.odm.test.utils;
import java.io.IOException;
import java.net.ServerSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// Added because the close down of the embedded Apache DS used
// for unit testing does not seem to free up its port.
public class GetFreePort {
private static Log LOG=LogFactory.getLog(GetFreePort.class);
public static int getFreePort()
throws IOException {
ServerSocket server = new ServerSocket(0);
int port = server.getLocalPort();
server.close();
LOG.debug(String.format("Port number: %1$s", port));
return port;
}
}

View File

@@ -0,0 +1,6 @@
package org.springframework.ldap.odm.test.utils;
// Interface to implement for tests to be run by ExecuteRunnable
public interface RunnableTest<T> {
void runTest(T testData) throws Exception;
}

View File

@@ -0,0 +1,7 @@
# List of attribute syntax to java class mappings
# Syntax Java class
# ------ ----------
1.3.6.1.4.1.1466.115.121.1.50, java.lang.Integer
1.3.6.1.4.1.1466.115.121.1.40, [B

View File

@@ -0,0 +1,142 @@
dn: ou=Enemies,o=Whoniverse
objectClass: organizationalUnit
objectClass: top
ou: Enemies
street: Acacia Avenue
description: The bad guys
dn: ou=Assistants,o=Whoniverse
objectClass: organizationalUnit
objectClass: top
ou: Assistants
street: Somewhere in space
description: The plucky helpers
dn: ou=Doctors,o=Whoniverse
objectClass: organizationalUnit
objectClass: top
ou: Doctors
street: Somewhere in time
description: Our hero
dn: cn=William Hartnell,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: William Hartnell
description: First Doctor
description: Grumpy
sn: Hartnell
telephonenumber: 1
dn: cn=Patrick Troughton,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Patrick Troughton
description: Second Doctor
description:Clown
sn: Troughton
telephonenumber: 2
dn: cn=Jon Pertwee,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Jon Pertwee
description: Third Doctor
description: Dandy
sn: Pertwee
telephonenumber: 3
dn: cn=Tom Baker,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Tom Baker
description: Fourth Doctor
description: The one and only!
sn: Baker
telephonenumber: 4
dn: cn=Peter Davison,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Peter Davison
description: Fifth Doctor
sn: Davison
telephonenumber: 5
dn: cn=Paul Harvey,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
objectClass: userSecurityInformation
cn: Paul Harvey
description: Not a Doctor
sn: Harvey
telephonenumber: 11
dn: cn=Bramble Harvey,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: top
cn: Bramble
description: Really not a Doctor
sn: Harvey
telephonenumber: 22
dn: cn=Davros,ou=Enemies,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Davros
description: Creator of the Daleks
description: Kaled head scientist
sn: Unknown
dn: cn=Daleks,ou=Enemies,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Daleks
description: The Doctor's greatest foe
sn: NA
dn: cn=Master,ou=Enemies,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Master
description: An evil Time Lord
sn: Unknown
jpegphoto:: /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkS
Ew8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRg
yIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wA
ARCAAnABoDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAA
gEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY
GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipK
TlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8v
P09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFB
AQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygp
KjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJm
aoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9
oADAMBAAIRAxEAPwDx2z0mK6gV/tSo2SCpUHGD9Qa2vDvgGfX7+eI3ot4EOEmEefMOOcZPQdOvW
s3VLGzsWiihUvM0ayONxATPIHXk4wfxFdd4L1aw0rw3eS3V3GHWRswkgugwCpVSckEk9OhB9azu
2ro6lCKnyyZwmvaHcaBrV1plxKkkluw+dRkMCAQfbgjiqi27sob1GfuVc1q9n1XVL3UdqxJI/wD
q/MXIXGAMd+B2HWoUZ1jUbZeABwKqzMJWT0Ld3cfaJ5ZFYtnCg+oUBf6V2XhvwVpcvhxfFXibVF
tNK3lEgjJ3yMGK7SRzkkHhRnHORXJQ6exVXB2r1K9Cfx7V0UWpeD7S3WJ9N1TUPLYssV7cBYkZg
AxAU4zwOcZ4HoKtppWQ4tSk5SNPx14o8M3/AITXT/D2kRRxB1jE4RIyuOc7ME84PzcHr2PPmX2y
YcZX8q37fQxPpqzsH8xvuqAeeuO/0H3e9Zn2dxwbK5JHXCH/AOJqNOhdSM1ZzVjRNwRbjGRxxzn
FdRouijV9Pi1C6toEMqn7MsK7SqBiCxOSSSVI5J4x6miiliW1FMzpbszreUw3H2VcuscpGT3UE9
Rn0HStE+IrZSQbEuRxvU4De4yc0UVFKCnuerms2vZryP/Z

View File

@@ -395,13 +395,28 @@
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.1</version>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.9</version>
</dependency>
<!-- Logging dependencies -->
<dependency>
<groupId>commons-logging</groupId>
@@ -433,6 +448,13 @@
<version>2.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jdepend</groupId>
<artifactId>jdepend</artifactId>
<version>2.9.1</version>
<type>jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>

View File

@@ -64,6 +64,12 @@
<contributor>
<name>Eric Dalquist</name>
</contributor>
<contributor>
<name>Keith Barlow</name>
</contributor>
<contributor>
<name>Paul Harvey</name>
</contributor>
</contributors>
<organization>
<name>The Spring LDAP Framework</name>
@@ -109,6 +115,7 @@
<module>parent</module>
<module>core</module>
<module>core-tiger</module>
<module>odm</module>
<module>test-support</module>
<module>test</module>
</modules>

104
samples/simple-odm/pom.xml Normal file
View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-parent</artifactId>
<version>1.3.1.CI-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-odm-sample</artifactId>
<name>Simple Spring LDAP ODM Sample</name>
<description>A very simple example of using the the Spring LDAP ODM</description>
<developers>
<developer>
<name>Paul Harvey</name>
<email>paul@pauls-place.me.uk</email>
<roles>
<role>Developer</role>
</roles>
<timezone>0</timezone>
</developer>
</developers>
<repositories>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.springframework.ldap.odm.sample.SearchForPeople</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<configuration>
<forkMode>always</forkMode>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-odm</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<type>jar</type>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-test</artifactId>
<version>${version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<type>jar</type>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,39 @@
package org.springframework.ldap.odm.sample;
import java.util.List;
import javax.naming.directory.SearchControls;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.odm.core.OdmManager;
// A very simple example - just showing how little code you actually need to write
// when using Spring LDAP ODM
public class SearchForPeople {
private static final SearchControls searchControls =
new SearchControls(SearchControls.SUBTREE_SCOPE, 100, 10000,
null, true, false);
private static final DistinguishedName baseDn = new DistinguishedName("o=Whoniverse");
private static void print(List<SimplePerson> personList) {
for (SimplePerson person : personList) {
System.out.println(person);
}
}
public static void main(String[] argv) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "spring.xml" });
// Grab the OdmManager wired by Spring
OdmManager odmManager = (OdmManager)context.getBean("odmManager");
// Find people with a surname of Harvey
List<SimplePerson> searchResults = odmManager.search(SimplePerson.class, baseDn, "sn=Harvey", searchControls);
// Print the results
print(searchResults);
}
}

View File

@@ -0,0 +1,218 @@
package org.springframework.ldap.odm.sample;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.HashSet;
import java.util.ArrayList;
import javax.naming.Name;
import static org.springframework.ldap.odm.annotations.Attribute.*;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
/**
* Automatically generated to represent the LDAP object classes
* "person", "top".
*/
@Entry(objectClasses={"person", "top"})
public final class SimplePerson {
@Id
private Name dn;
@Attribute(name="objectClass", syntax="1.3.6.1.4.1.1466.115.121.1.38")
private List<String> objectClass=new ArrayList<String>();
@Attribute(name="cn", syntax="1.3.6.1.4.1.1466.115.121.1.15")
private List<String> cn=new ArrayList<String>();
@Attribute(name="sn", syntax="1.3.6.1.4.1.1466.115.121.1.15")
private List<String> sn=new ArrayList<String>();
@Attribute(name="description", syntax="1.3.6.1.4.1.1466.115.121.1.15")
private List<String> description=new ArrayList<String>();
@Attribute(name="userPassword", syntax="1.3.6.1.4.1.1466.115.121.1.40", type=Type.BINARY)
private List<byte[]> userPassword=new ArrayList<byte[]>();
@Attribute(name="telephoneNumber", syntax="1.3.6.1.4.1.1466.115.121.1.50")
private List<String> telephoneNumber=new ArrayList<String>();
@Attribute(name="seeAlso", syntax="1.3.6.1.4.1.1466.115.121.1.12")
private List<String> seeAlso=new ArrayList<String>();
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn=dn;
}
public Iterator<String> getObjectClassIterator() {
return Collections.unmodifiableList(objectClass).iterator();
}
public void addCn(String cn) {
this.cn.add(cn);
}
public void removeCn(String cn) {
this.cn.remove(cn);
}
public Iterator<String> getCnIterator() {
return cn.iterator();
}
public void addSn(String sn) {
this.sn.add(sn);
}
public void removeSn(String sn) {
this.sn.remove(sn);
}
public Iterator<String> getSnIterator() {
return sn.iterator();
}
public void addDescription(String description) {
this.description.add(description);
}
public void removeDescription(String description) {
this.description.remove(description);
}
public Iterator<String> getDescriptionIterator() {
return description.iterator();
}
public void addUserPassword(byte[] userPassword) {
this.userPassword.add(userPassword);
}
public void removeUserPassword(byte[] userPassword) {
this.userPassword.remove(userPassword);
}
public Iterator<byte[]> getUserPasswordIterator() {
return userPassword.iterator();
}
public void addTelephoneNumber(String telephoneNumber) {
this.telephoneNumber.add(telephoneNumber);
}
public void removeTelephoneNumber(String telephoneNumber) {
this.telephoneNumber.remove(telephoneNumber);
}
public Iterator<String> getTelephoneNumberIterator() {
return telephoneNumber.iterator();
}
public void addSeeAlso(String seeAlso) {
this.seeAlso.add(seeAlso);
}
public void removeSeeAlso(String seeAlso) {
this.seeAlso.remove(seeAlso);
}
public Iterator<String> getSeeAlsoIterator() {
return seeAlso.iterator();
}
@Override
public String toString() {
StringBuilder result=new StringBuilder();
result.append(String.format("dn=%1$s", dn));
result.append(String.format(" | objectClass=%1$s", objectClass));
result.append(String.format(" | cn=%1$s", cn));
result.append(String.format(" | sn=%1$s", sn));
result.append(String.format(" | description=%1$s", description));
result.append(String.format(" | userPassword=%1$s", userPassword));
result.append(String.format(" | telephoneNumber=%1$s", telephoneNumber));
result.append(String.format(" | seeAlso=%1$s", seeAlso));
return result.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dn == null) ? 0 : dn.hashCode());
result = prime * result + ((objectClass == null) ? 0 : (new HashSet<String>(objectClass)).hashCode());
result = prime * result + ((cn == null) ? 0 : (new HashSet<String>(cn)).hashCode());
result = prime * result + ((sn == null) ? 0 : (new HashSet<String>(sn)).hashCode());
result = prime * result + ((description == null) ? 0 : (new HashSet<String>(description)).hashCode());
result = prime * result + ((userPassword == null) ? 0 : (new HashSet<byte[]>(userPassword)).hashCode());
result = prime * result + ((telephoneNumber == null) ? 0 : (new HashSet<String>(telephoneNumber)).hashCode());
result = prime * result + ((seeAlso == null) ? 0 : (new HashSet<String>(seeAlso)).hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SimplePerson other = (SimplePerson) obj;
if (dn == null) {
if (other.dn != null)
return false;
} else if (!dn.equals(other.dn))
return false;
if (objectClass == null) {
if (other.objectClass != null)
return false;
} else if (!(new HashSet<String>(objectClass)).equals(new HashSet<String>(other.objectClass)))
return false;
if (cn == null) {
if (other.cn != null)
return false;
} else if (!(new HashSet<String>(cn)).equals(new HashSet<String>(other.cn)))
return false;
if (sn == null) {
if (other.sn != null)
return false;
} else if (!(new HashSet<String>(sn)).equals(new HashSet<String>(other.sn)))
return false;
if (description == null) {
if (other.description != null)
return false;
} else if (!(new HashSet<String>(description)).equals(new HashSet<String>(other.description)))
return false;
if (userPassword == null) {
if (other.userPassword != null)
return false;
} else if (!(new HashSet<byte[]>(userPassword)).equals(new HashSet<byte[]>(other.userPassword)))
return false;
if (telephoneNumber == null) {
if (other.telephoneNumber != null)
return false;
} else if (!(new HashSet<String>(telephoneNumber)).equals(new HashSet<String>(other.telephoneNumber)))
return false;
if (seeAlso == null) {
if (other.seeAlso != null)
return false;
} else if (!(new HashSet<String>(seeAlso)).equals(new HashSet<String>(other.seeAlso)))
return false;
return true;
}
}

View File

@@ -0,0 +1,7 @@
/**
* A very simple example of the use of Spring ODM.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
package org.springframework.ldap.odm.sample;

View File

@@ -0,0 +1,19 @@
log4j.rootLogger=error, stdout
log4j.logger.org.springframework.ldap.odm=error
log4j.logger.org.springframework=error
# Messages to the console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
# Message to a log file
log4j.appender.log=org.apache.log4j.RollingFileAppender
log4j.appender.log.File=jndi.log
log4j.appender.log.MaxFileSize=100KB
log4j.appender.log.MaxBackupIndex=1
log4j.appender.log.layout=org.apache.log4j.PatternLayout
log4j.appender.log.layout.ConversionPattern=%p %t %c - %m%n

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- An example Spring configuration for Spring LDAP ODM -->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<bean id="fromStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter" />
<bean id="toStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter" />
<bean id="converterManager" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
<property name="converterConfig">
<set>
<bean class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
<property name="fromClasses">
<set>
<value>java.lang.String</value>
</set>
</property>
<property name="toClasses">
<set>
<value>java.lang.Byte</value>
<value>java.lang.Short</value>
<value>java.lang.Integer</value>
<value>java.lang.Long</value>
<value>java.lang.Float</value>
<value>java.lang.Double</value>
<value>java.lang.Boolean</value>
</set>
</property>
<property name="converter" ref="fromStringConverter" />
</bean>
<bean class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
<property name="fromClasses">
<set>
<value>java.lang.Byte</value>
<value>java.lang.Short</value>
<value>java.lang.Integer</value>
<value>java.lang.Long</value>
<value>java.lang.Float</value>
<value>java.lang.Double</value>
<value>java.lang.Boolean</value>
</set>
</property>
<property name="toClasses">
<set>
<value>java.lang.String</value>
</set>
</property>
<property name="converter" ref="toStringConverter" />
</bean>
</set>
</property>
</bean>
<bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
<property name="url" value="ldap://127.0.0.1:10389" />
<property name="userDn" value="uid=admin,ou=system" />
<property name="password" value="secret" />
<property name="pooled" value="false" />
</bean>
<bean id="dirContextValidator" class="org.springframework.ldap.pool.validation.DefaultDirContextValidator">
<property name="base" value="" />
<property name="filter" value="objectclass=*" />
<property name="searchControls.searchScope">
<util:constant static-field="javax.naming.directory.SearchControls.OBJECT_SCOPE" />
</property>
</bean>
<bean id="contextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
<property name="contextSource" ref="contextSourceTarget" />
<property name="dirContextValidator" ref="dirContextValidator" />
<property name="maxActive" value="10" />
<property name="maxTotal" value="10" />
<property name="maxIdle" value="5" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
</bean>
<bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean">
<property name="converterManager" ref="converterManager" />
<property name="contextSource" ref="contextSource" />
<property name="managedClasses">
<set>
<value>org.springframework.ldap.odm.sample.SimplePerson</value>
</set>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,79 @@
package org.springframework.ldap.odm.sample.test;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.odm.sample.SearchForPeople;
import org.springframework.ldap.test.LdapTestUtils;
public class TestSearchForPeople {
// Base DN for test data
private static final DistinguishedName baseName = new DistinguishedName("o=Whoniverse");
private static final String PRINCIPAL="uid=admin,ou=system";
private static final String CREDENTIALS="secret";
// This port MUST be free on local host for these unit tests to function.
private static int PORT=10389;
@BeforeClass
public static void setUpClass() throws Exception {
// Start an LDAP server and import test data
LdapTestUtils.startApacheDirectoryServer(PORT, baseName.toString(), "odm-test", PRINCIPAL, CREDENTIALS, null);
}
@AfterClass
public static void tearDownClass() throws Exception {
LdapTestUtils.destroyApacheDirectoryServer(PRINCIPAL, CREDENTIALS);
}
@Before
public void setUp() throws Exception {
// Bind to the directory
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl("ldap://127.0.0.1:" + PORT);
contextSource.setUserDn("");
contextSource.setPassword("");
contextSource.setPooled(false);
contextSource.afterPropertiesSet();
// Create the Sprint LDAP template
LdapTemplate template = new LdapTemplate(contextSource);
// Clear out any old data - and load the test data
LdapTestUtils.cleanAndSetup(template.getContextSource(), baseName, new ClassPathResource("testdata.ldif"));
}
@After
public void tearDown() {
}
// Very simple test - mainly just to exercise the code and to
// ensure we get representative test coverage
@Test
public void runSample() throws Exception {
PrintStream originalOut=System.out;
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
System.setOut(new PrintStream(output));
SearchForPeople.main(null);
assertEquals("dn=cn=Bramble Harvey,ou=Doctors,o=Whoniverse | objectClass=[person, top] | cn=[Bramble Harvey] | sn=[Harvey] | description=[Really not a Doctor] | userPassword=[] | telephoneNumber=[22] | seeAlso=[]",
output.toString().trim());
} finally {
System.setOut(originalOut);
}
}
}

View File

@@ -0,0 +1,116 @@
dn: ou=Enemies,o=Whoniverse
objectClass: organizationalUnit
objectClass: top
ou: Blizzard
dn: ou=Assistants,o=Whoniverse
objectClass: organizationalUnit
objectClass: top
ou: Blizzard
dn: ou=Doctors,o=Whoniverse
objectClass: organizationalUnit
objectClass: top
ou: NCSoft
dn: cn=William Hartnell,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: William Hartnell
description: First Doctor
description: Grumpy
sn: Hartnell
telephonenumber: 1
dn: cn=Patrick Troughton,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Patrick Troughton
description: Second Doctor
description:Clown
sn: Troughton
telephonenumber: 2
dn: cn=Jon Pertwee,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Jon Pertwee
description: Third Doctor
description: Dandy
sn: Pertwee
telephonenumber: 3
dn: cn=Tom Baker,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Tom Baker
description: Fourth Doctor
description: The one and only!
sn: Baker
telephonenumber: 4
dn: cn=Peter Davison,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Peter Davison
description: Fifth Doctor
sn: Davison
telephonenumber: 5
dn: cn=Paul Harvey,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
objectClass: userSecurityInformation
cn: Paul Harvey
description: Not a Doctor
sn: Harvey
telephonenumber: 11
dn: cn=Bramble Harvey,ou=Doctors,o=Whoniverse
objectClass: person
objectClass: top
cn: Bramble
description: Really not a Doctor
sn: Harvey
telephonenumber: 22
dn: cn=Davros,ou=Enemies,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Davros
description: Creator of the Daleks
description: Kaled head scientist
sn: Unknown
dn: cn=Daleks,ou=Enemies,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Daleks
description: The Doctor's greatest foe
sn: NA
dn: cn=Master,ou=Enemies,o=Whoniverse
objectClass: person
objectClass: inetorgperson
objectclass: organizationalperson
objectClass: top
cn: Master
description: An evil Time Lord
sn: Unknown

View File

@@ -10,6 +10,7 @@
<includes>
<include>org.springframework.ldap:spring-ldap-core</include>
<include>org.springframework.ldap:spring-ldap-core-tiger</include>
<include>org.springframework.ldap:spring-ldap-odm</include>
<include>org.springframework.ldap:spring-ldap-test</include>
</includes>
<binaries>

View File

@@ -13,6 +13,7 @@
<includes>
<include>org.springframework.ldap:spring-ldap-core</include>
<include>org.springframework.ldap:spring-ldap-core-tiger</include>
<include>org.springframework.ldap:spring-ldap-odm</include>
<include>org.springframework.ldap:spring-ldap-test</include>
</includes>
<binaries>

View File

@@ -14,6 +14,10 @@
<directory>core-tiger/src/main/java</directory>
<outputDirectory/>
</fileSet>
<fileSet>
<directory>odm/src/main/java</directory>
<outputDirectory/>
</fileSet>
<fileSet>
<directory>test-support/src/main/java</directory>
<outputDirectory/>

View File

@@ -13,6 +13,7 @@
<include>org.springframework.ldap:spring-ldap-parent</include>
<include>org.springframework.ldap:spring-ldap-core</include>
<include>org.springframework.ldap:spring-ldap-core-tiger</include>
<include>org.springframework.ldap:spring-ldap-odm</include>
<include>org.springframework.ldap:spring-ldap-test</include>
</includes>
<binaries>