Added LDIF parser files from Keith Barlow.

This commit is contained in:
Ulrik Sandberg
2009-08-02 19:30:55 +00:00
parent 69ad3a295e
commit 2d4a185186
20 changed files with 1985 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
/**
*
*/
package org.springframework.ldap.ldif;
import org.springframework.ldap.NamingException;
/**
* Thrown whenever a parsed attribute does not conform to LDAP specifications.
*
* @author Keith Barlow
*
*/
public class InvalidAttributeFormatException extends NamingException {
private static final long serialVersionUID = -4529380160785322985L;
/**
* @param msg
*/
public InvalidAttributeFormatException(String msg) {
super(msg);
}
/**
* @param cause
*/
public InvalidAttributeFormatException(Throwable cause) {
super(cause);
}
/**
* @param msg
* @param cause
*/
public InvalidAttributeFormatException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -0,0 +1,40 @@
/**
*
*/
package org.springframework.ldap.ldif;
import org.springframework.ldap.NamingException;
/**
* Thrown whenever a parsed record does not conform to LDAP specifications.
*
* @author Keith Barlow
*
*/
public class InvalidRecordFormatException extends NamingException {
private static final long serialVersionUID = -5047874723621065139L;
/**
* @param msg
*/
public InvalidRecordFormatException(String msg) {
super(msg);
}
/**
* @param cause
*/
public InvalidRecordFormatException(Throwable cause) {
super(cause);
}
/**
* @param msg
* @param cause
*/
public InvalidRecordFormatException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -0,0 +1,212 @@
/**
* Extends BasicAttribute adding support for options.
*/
package org.springframework.ldap.ldif;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.naming.directory.BasicAttribute;
/**
* Extends {@link javax.naming.directory.BasicAttribute} to add support for
* options as defined in RFC2849.
* <p>
* While uncommon, options can be used to specify additional descriptors for
* the attribute. Options are backed by a {@link java.util.HashSet} of
* {@link java.lang.String}.
*
* @author Keith Barlow
*
*/
public class LdapAttribute extends BasicAttribute {
private static final long serialVersionUID = -5263905906016179429L;
/**
* Holds the attributes options.
*/
protected Set<String> options = new HashSet<String>();
/**
* Creates an unordered attribute with the specified ID.
*
* @param id {@link java.lang.String} ID of the attribute.
*/
public LdapAttribute(String id) {
super(id);
}
/**
* Creates an unordered attribute with the specified ID and value.
*
* @param id {@link java.lang.String} ID of the attribute.
* @param value Attribute value.
*/
public LdapAttribute(String id, Object value) {
super(id, value);
}
/**
* Creates an unordered attribute with the specified ID, value, and options.
*
* @param id {@link java.lang.String} ID of the attribute.
* @param value Attribute value.
* @param options {@link java.util.Collection} of {@link java.lang.String} attribute options.
*/
public LdapAttribute(String id, Object value, Collection<String> options) {
super(id, value);
this.options.addAll(options);
}
/**
* Creates an attribute with the specified ID whose values may be ordered.
*
* @param id {@link java.lang.String} ID of the attribute.
* @param ordered {@link java.lang.boolean} indicating whether or not the attributes values are ordered.
*/
public LdapAttribute(String id, boolean ordered) {
super(id, ordered);
}
/**
* Creates an attribute with the specified ID and options whose values may be ordered.
*
* @param id {@link java.lang.String} ID of the attribute.
* @param options {@link java.util.Collection} of {@link java.lang.String} attribute options.
* @param ordered {@link java.lang.boolean} indicating whether or not the attributes values are ordered.
*/
public LdapAttribute(String id, Collection<String> options, boolean ordered) {
super(id, ordered);
this.options.addAll(options);
}
/**
* Creates an attribute with the specified ID and value whose values may be ordered.
*
* @param id {@link java.lang.String} ID of the attribute.
* @param value Attribute value.
* @param ordered {@link java.lang.boolean} indicating whether or not the attributes values are ordered.
*/
public LdapAttribute(String id, Object value, boolean ordered) {
super(id, value, ordered);
}
/**
* Creates an attribute with the specified ID, value, and options whose values may be ordered.
*
* @param id {@link java.lang.String} ID of the attribute.
* @param value Attribute value.
* @param options {@link java.util.Collection} of {@link java.lang.String} attribute options.
* @param ordered {@link java.lang.boolean} indicating whether or not the attributes values are ordered.
*/
public LdapAttribute(String id, Object value, Collection<String> options, boolean ordered) {
super(id, value, ordered);
this.options.addAll(options);
}
/**
* Get options.
*
* @return returns a {@link java.util.Set} of {@link java.lang.String}
*/
public Set<String> getOptions() {
return this.options;
}
/**
* Set options.
*
* @param options {@link java.util.Set} of {@link java.lang.String}
*/
public void setOptions(Set<String> options) {
this.options = options;
}
/**
* Add an option.
*
* @param option {@link java.lang.String} option.
* @return {@link java.lang.boolean} indication successful addition of option.
*/
public boolean addOption(String option) {
return this.options.add(option);
}
/**
* Add all values in the collection to the options.
*
* @param options {@link java.util.Collection} of {@link java.lang.String} values.
* @return {@link java.lang.boolean} indication successful addition of options.
*/
public boolean addAllOptions(Collection<String> options) {
return this.options.addAll(options);
}
/**
* Clears all stored options.
*/
public void clearOptions() {
this.options.clear();
}
/**
* Checks for existence of a particular option on the set.
*
* @param option {@link java.lang.String} option.
* @return {@link java.lang.boolean} indicating result.
*/
public boolean contains(String option) {
return this.options.contains(option);
}
/**
* Checks for existence of a series of options on the set.
*
* @param options {@link java.util.Collection} of {@link java.lang.String} options.
* @return {@link java.lang.boolean} indicating result.
*/
public boolean containsAll(Collection<String> options) {
return this.options.containsAll(options);
}
/**
* Tests for the presence of options.
*
* @return {@link java.lang.boolean} indicating result.
*/
public boolean hasOptions() {
return !options.isEmpty();
}
/**
* Removes an option from the the set.
*
* @param option {@link java.lang.String} option.
* @return {@link java.lang.boolean} indicating successful removal of option.
*/
public boolean removeOption(String option) {
return this.options.remove(options);
}
/**
* Removes all options listed in the supplied set.
*
* @param options {@link java.util.Collection} of {@link java.util.String} options.
* @return {@link java.lang.boolean} indicating successful removal of options.
*/
public boolean removeAllOptions(Collection<String> options) {
return this.options.removeAll(options);
}
/**
* Removes any options not on the set of supplied options.
*
* @param options {@link java.util.Collection} of {@link java.util.String} options.
* @return {@link java.lang.boolean} indicating successful retention of options.
*/
public boolean retainAllOptions(Collection<String> options) {
return this.options.retainAll(options);
}
}

View File

@@ -0,0 +1,194 @@
/**
* Extends BasicAttributes adding support for DNs.
*/
package org.springframework.ldap.ldif;
import java.net.URI;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttributes;
import org.springframework.ldap.core.DistinguishedName;
import sun.misc.BASE64Encoder;
/**
* Extends {@link javax.naming.directory.BasicAttributes} to add specialized support
* for DNs.
* <p>
* While DNs appear to be and can be treated as attributes, they have a special
* meaning in that they define the address to which the object is bound. DNs must
* conform to special formating rules and are typically required to be handled
* separately from other attributes.
* <p>
* This class makes this distinction between the DN and other
* attributes prominent and apparent.
*
* @author Keith Barlow
*
*/
public class LdapAttributes extends BasicAttributes {
private static final long serialVersionUID = 97903297123869138L;
private static final String SAFE_CHAR = "[\\p{ASCII}&&[^\\x00\\x0A\\x0D]]"; //Any ASCII except NUL, LF, and CR
private static final String SAFE_INIT_CHAR = "[\\p{ASCII}&&[^ \\x00\\x0A\\x0D\\x3A\\x3C]]"; //Any ASCII except NUL, LF, CR, SPACE, colon, and less-than
/**
* Distinguished name to which the object is bound.
*/
protected DistinguishedName dn = new DistinguishedName();
/**
* Default constructor.
*/
public LdapAttributes() {
}
/**
* Creates an LdapAttributes object with the specified DN.
*
* @param dn The {@link org.springframework.ldap.core.DistinguishedName} to which this object is bound.
*/
public LdapAttributes(DistinguishedName dn) {
super();
this.dn = dn;
}
/**
* Constructor for specifying whether or not the object is case sensitive.
*
* @param ignoreCase {@link java.lang.boolean} indicator.
*/
public LdapAttributes(boolean ignoreCase) {
super(ignoreCase);
}
/**
* Creates an LdapAttributes object with the specified DN and case sensitivity setting.
*
* @param dn The {@link org.springframework.ldap.core.DistinguishedName} to which this object is bound.
* @param ignoreCase {@link java.lang.boolean} indicator.
*/
public LdapAttributes(DistinguishedName dn, boolean ignoreCase) {
super(ignoreCase);
this.dn = dn;
}
/**
* Creates an LdapAttributes object with the specified attribute.
*
* @param attrID {@link java.lang.String} ID of the attribute.
* @param val Value of the attribute.
*/
public LdapAttributes(String attrID, Object val) {
put(new LdapAttribute(attrID, val));
}
/**
* Creates an LdapAttributes object with the specifying attribute and value and case sensitivity setting.
*
* @param dn The {@link org.springframework.ldap.core.DistinguishedName} to which this object is bound.
* @param attrID {@link java.lang.String} ID of the attribute.
* @param val Value of the attribute.
*/
public LdapAttributes(DistinguishedName dn, String attrID, Object val) {
this.dn = dn;
put(new LdapAttribute(attrID, val));
}
/**
* Creates an LdapAttributes object with the specifying attribute and value and case sensitivity setting.
*
* @param attrID {@link java.lang.String} ID of the attribute.
* @param val Value of the attribute.
* @param ignoreCase {@link java.lang.boolean} indicator.
*/
public LdapAttributes(String attrID, Object val, boolean ignoreCase) {
put(new LdapAttribute(attrID, val, ignoreCase));
}
/**
* Creates an LdapAttributes object for the supplied DN with the attribute specified.
*
* @param dn The {@link org.springframework.ldap.core.DistinguishedName} to which this object is bound.
* @param attrID {@link java.lang.String} ID of the attribute.
* @param val Value of the attribute.
* @param ignoreCase {@link java.lang.boolean} indicator.
*/
public LdapAttributes(DistinguishedName dn, String attrID, Object val, boolean ignoreCase) {
this.dn = dn;
put(new LdapAttribute(attrID, val, ignoreCase));
}
/**
* Returns the distinguished name to which the object is bound.
*
* @return {@link org.springframework.ldap.core.DistinguishedName} specifying the name to which the object is bound.
*/
public DistinguishedName getDN() {
return dn;
}
/**
* Sets the distinguished name of the object.
*
* @param dn {@link org.springframework.ldap.core.DistinguishedName} specifying the name to which the object is bound.
*/
public void setDN(DistinguishedName dn) {
this.dn = dn;
}
/**
* Returns a string representation of the object in LDIF format.
*
* @return {@link java.lang.String} formated to RFC2849 LDIF specifications.
*/
public String toString() {
try {
StringBuilder sb = new StringBuilder();
DistinguishedName dn = getDN();
if (!dn.toString().matches(SAFE_INIT_CHAR + SAFE_CHAR + "*")) {
sb.append("dn:: " + new BASE64Encoder().encode(dn.toString().getBytes()) + "\n");
} else {
sb.append("dn: " + getDN() + "\n");
}
NamingEnumeration<Attribute> attributes = getAll();
while (attributes.hasMore()) {
Attribute attribute = attributes.next();
NamingEnumeration<?> values = attribute.getAll();
while (values.hasMore()) {
Object value = values.next();
if (value instanceof String)
sb.append(attribute.getID() + ": " + (String) value + "\n");
else if (value instanceof byte[])
sb.append(attribute.getID() + ":: " + new BASE64Encoder().encode((byte[]) value) + "\n");
else if (value instanceof URI)
sb.append(attribute.getID() + ":< " + (URI) value + "\n");
else {
sb.append(attribute.getID() + ": " + value + "\n");
}
}
}
return sb.toString();
} catch (NamingException e) {
e.printStackTrace();
return "";
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* The base package for Spring LDAPs LDIF parser implementation.
* <p>
* Classes declared in this package include the new base types for
* LDAP objects as well as exception types for the LDIF parser.
*/
package org.springframework.ldap.ldif;

View File

@@ -0,0 +1,320 @@
package org.springframework.ldap.ldif.parser;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.NoSuchElementException;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.ldif.InvalidAttributeFormatException;
import org.springframework.ldap.ldif.InvalidRecordFormatException;
import org.springframework.ldap.ldif.LdapAttributes;
import org.springframework.ldap.ldif.support.AttributeValidationPolicy;
import org.springframework.ldap.ldif.support.DefaultAttributeValidationPolicy;
import org.springframework.ldap.ldif.support.LineIdentifier;
import org.springframework.ldap.ldif.support.SeparatorPolicy;
import org.springframework.ldap.schema.DefaultSchemaSpecification;
import org.springframework.ldap.schema.Specification;
import org.springframework.util.Assert;
/**
* The {@link LDIFParser LDIFParser} is the main class of the {@link org.springframework.ldap.ldif} package.
* This class reads lines from a resource and assembles them into an {@link LdapAttributes LdapAttributes} object.
* The {@link LDIFParser LDIFParser} does ignores <i>changetype</i> LDIF entries as their usefulness in the
* context of an application has yet to be determined.
* <p>
* <b>Design</b><br/>
* {@link LDIFPaser LDIFParser} provides the main interface for operation but requires three supporting classes to
* enable operation:
* <ul>
* <li>{@link SeparatorPolicy SeparatorPolicy} - establishes the mechanism by which lines are assembled into attributes.</li>
* <li>{@link AttributeValidationPolicy AttributeValidationPolicy} - ensures that attributes are correctly structured prior to parsing.</li>
* <li>{@link Specification Specification} - provides a mechanism by which object structure can be validated after assembly.</li>
* </ul>
* Together, these 4 classes read from the resource line by line and translate the data into objects for use.
* <p>
* <b>Usage</b><br/>
* {@link #getRecord() getRecord()} reads the next available record from the resource. Lines are read and
* passed to the {@link SeparatorPolicy SeparatorPolicy} for interpretation. The parser continues to read
* lines and appends them to the buffer until it encounters the start of a new attribute or an end of record
* delimiter. When the new attribute or end of record is encountered, the buffer is passed to the
* {@link AttributeValidationPolicy AttributeValidationPolicy} which ensures the buffer conforms to a valid
* attribute definition as defined in RFC2849 and returns an {@link org.springframework.ldap.ldif.LdapAttribute LdapAttribute} object
* which is then added to the record, an {@link LdapAttributes LdapAttributes} object. Upon encountering the
* end of record, the record is validated by the {@link Specification Specification} policy and,
* if valid, returned to the requester.
* <p>
* The parser requires the resource to be {@link open() open()} prior to an invocation of {@link #getRecord() getRecord()}.
* {@link #hasMoreRecords() hasMoreRecords()} can be used to loop over the resource until all records have been
* retrieved. Likewise, the {@link #reset() reset()} method will reset the resource.
*
* @author Keith Barlow
*
*/
public class LDIFParser implements Parser {
private static final Log log = LogFactory.getLog(LDIFParser.class);
/**
* The resource to parse.
*/
private Resource resource;
/**
* A BufferedReader to read the file.
*/
private BufferedReader reader;
/**
* The SeparatorPolicy to use for interpreting attributes from the lines of the resource.
*/
private SeparatorPolicy separatorPolicy = new SeparatorPolicy();
/**
* The AttributeValidationPolicy to use to interpret attributes.
*/
private AttributeValidationPolicy attributePolicy = new DefaultAttributeValidationPolicy();
/**
* The RecordSpecification for validating records produced.
*/
private Specification<LdapAttributes> recordSpecification = new DefaultSchemaSpecification();
/**
* Default constructor.
*/
public LDIFParser() {
}
/**
* Convenience constructor for resource specification.
*
* @param resource The resource to parse.
*/
public LDIFParser(Resource resource) {
this.resource = resource;
}
/**
* Convenience constructor: accepts a File object.
*
* @param file The file to parse.
*/
public LDIFParser(File file) {
this.resource = new FileSystemResource(file);
}
/**
* Set the separator policy.
*
* The default separator policy should suffice for most needs.
*
* @param separatorPolicy Separator policy.
*/
public void setSeparatorPolicy(SeparatorPolicy separatorPolicy) {
this.separatorPolicy = separatorPolicy;
}
/**
* Policy object enforcing the rules for acceptable attributes.
*
* @param avPolicy Attribute validation policy.
*/
public void setAttributeValidationPolicy(AttributeValidationPolicy avPolicy) {
this.attributePolicy = avPolicy;
}
/**
* Policy object for enforcing rules to acceptable LDAP objects.
*
* This policy may be used to enforce schema restrictions.
* @param specification
*/
public void setRecordSpecification(Specification<LdapAttributes> specification) {
this.recordSpecification = specification;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public void afterPropertiesSet() throws Exception {
}
public void open() throws IOException {
Assert.notNull(resource, "Resource must be set.");
Assert.isTrue(resource.exists(), resource.getDescription() + ": resource does not exist!");
Assert.isTrue(resource.isReadable(), "Resource is not readable.");
reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
}
public void close() throws IOException {
if (resource.isOpen())
reader.close();
}
public void reset() throws IOException {
Assert.notNull(reader, "A reader has not been obtained.");
reader.reset();
}
public boolean hasMoreRecords() throws IOException {
return reader.ready();
}
public LdapAttributes getRecord() throws IOException {
Assert.notNull(reader, "A reader must be obtained: parser not open.");
if (!reader.ready()) return null;
LdapAttributes record = new LdapAttributes();
StringBuilder builder = new StringBuilder();
String line = reader.readLine();
while(true) {
LineIdentifier identifier = separatorPolicy.assess(line);
switch(identifier) {
case NewRecord:
log.trace("Starting new record.");
//Start new record.
builder = new StringBuilder(line);
break;
case Control:
log.trace("'control' encountered.");
//Log WARN and discard record.
log.warn("LDIF change records have no implementation: record will be ignored.");
builder = null;
record = null;
break;
case ChangeType:
log.trace("'changetype' encountered.");
//Log WARN and discard record.
log.warn("LDIF change records have no implementation: record will be ignored.");
builder = null;
record = null;
break;
case Attribute:
//flush buffer.
addAttributeToRecord(builder.toString(), record);
log.trace("Starting new attribute.");
//Start new attribute.
builder = new StringBuilder(line);
break;
case Continuation:
log.trace("...appending line to buffer.");
//Append line to buffer.
builder.append(line.replaceFirst(" ", ""));
break;
case EndOfRecord:
log.trace("...done parsing record. (EndOfRecord)");
//Validate record and return.
if (record == null) return null;
else {
try {
//flush buffer.
addAttributeToRecord(builder.toString(), record);
log.debug("satisfied: " + recordSpecification.isSatisfiedBy(record));
if (recordSpecification.isSatisfiedBy(record)) {
log.trace("Returning record.");
return record;
} else {
String dn;
try {
dn = (String) record.get("dn").get();
} catch (NamingException e) {
dn = "";
}
throw new InvalidRecordFormatException("Record [dn: " + dn + "] does not conform to specification.");
}
} catch(NamingException e) {
log.error(e);
return null;
}
}
default:
//Take no action -- applies to VersionIdentifier, Comments, and voided records.
}
line = reader.readLine();
}
}
private void addAttributeToRecord(String buffer, LdapAttributes record) {
try {
if (StringUtils.isNotEmpty(buffer) && record != null) {
//Validate previous attribute and add to record.
Attribute attribute = attributePolicy.parse(buffer);
if (attribute.getID().equalsIgnoreCase("dn")) {
log.trace("...adding DN to record.");
String dn;
if (attribute.get() instanceof byte[]) {
dn = new String((byte[]) attribute.get());
} else {
dn = (String) attribute.get();
}
record.setDN(new DistinguishedName(dn));
} else {
log.trace("...adding attribute to record.");
Attribute attr = record.get(attribute.getID());
if (attr != null) {
attr.add(attribute.get());
} else {
record.put(attribute);
}
}
}
} catch (NamingException e) {
log.error(e);
} catch (NoSuchElementException e) {
log.error(e);
} catch (InvalidAttributeFormatException e) {
log.error(e);
record = null;
}
}
}

View File

@@ -0,0 +1,62 @@
package org.springframework.ldap.ldif.parser;
import java.io.IOException;
import javax.naming.directory.Attributes;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
/**
* The Parser interface represents the required methods to be implemented by parser utilities.
* These methods are the base set of methods needed to provide parsing ability.
* @author Keith Barlow
*
*/
public interface Parser extends InitializingBean {
/**
* Sets the resource to parse.
*
* @param resource The resource to parse.
*/
public void setResource(Resource resource);
/**
* Opens the resource: the resource must be opened prior to parsing.
*
* @throws IOException if a problem is encountered while trying to open the resource.
*/
public void open() throws IOException;
/**
* Closes the resource after parsing.
*
* @throws IOException if a problem is encountered while trying to close the resource.
*/
public void close() throws IOException;
/**
* Resets the line read parser.
*
* @throws Exception if a problem is encountered while trying to reset the resource.
*/
public void reset() throws IOException;
/**
* True if the resource contains more records; false otherwise.
*
* @return boolean indicating whether or not the end of record has been reached.
* @throws IOException if a problem is encountered while trying to validate the resource is ready.
*/
public boolean hasMoreRecords() throws IOException;
/**
* Parses the next record from the resource.
*
* @return LdapAttributes object representing the record parsed.
* @throws IOException if a problem is encountered while trying to read from the resource.
*/
public Attributes getRecord() throws IOException;
}

View File

@@ -0,0 +1,4 @@
/**
* This package contains the parser classes and interfaces.
*/
package org.springframework.ldap.ldif.parser;

View File

@@ -0,0 +1,20 @@
package org.springframework.ldap.ldif.support;
import javax.naming.directory.Attribute;
/**
* Interface defining the required methods for AttributeValidationPolicies.
* @author Keith Barlow
*
*/
public interface AttributeValidationPolicy {
/**
* Validates attribute contained in the buffer and returns an LdapAttribute.
*
* @param buffer
* @return LdapAttribute representing the attribute parsed.
*/
public Attribute parse(String buffer);
}

View File

@@ -0,0 +1,356 @@
/**
* Attribute validation policy
*
* Meets the standards imposed by RFC 2849 for the "LDAP Data Interchange Format (LDIF)
* - Technical Specification".
*
* Special attention is called to URL support: RFC 2849 requires that
* LDIFs support URLs as defined in 1738; however, RFC 1738 has been updated by several RFCs including
* RFC 1808, RFC 2396, and RFC 3986 (which obsoleted the formers). Unsupported features of this
* implementation of URL identification include query strings and fragments in HTTP URLs.
*
*/
package org.springframework.ldap.ldif.support;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.directory.Attribute;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.ldif.InvalidAttributeFormatException;
import org.springframework.ldap.ldif.LdapAttribute;
import sun.misc.BASE64Decoder;
/**
* Ensures the buffer represents a valid attribute as defined by RFC2849.
*
* @author Keith Barlow
*
*/
@SuppressWarnings("unused")
public class DefaultAttributeValidationPolicy implements AttributeValidationPolicy {
private static Log log = LogFactory.getLog(DefaultAttributeValidationPolicy.class);
/**
* Pattern Declarations.
*/
//General Definitions
private static final String DIGIT = "\\p{Digit}";
private static final String LOW_ALPHA = "\\p{Lower}";
private static final String HIGH_ALPHA = "\\p{Upper}";
private static final String ALPHA = "\\p{Alpha}";
private static final String ALPHANUM = "\\p{Alnum}";
private static final String HEX = "\\p{XDigit}";
private static final String SAFE = "[\\x24\\x2D\\x5F\\x2E\\x2B]"; //$|-|_|.|+
private static final String EXTRA = "[\\x21\\x2A\\x27\\x7B\\x7D\\x2C]"; //!|*|'|(|)|,
private static final String PUNCTUATION = "[\\x3C\\x3E\\x23\\x25\\x22]"; //<|>|#|%|"
private static final String ESCAPE = "%" + HEX + "{2}";
private static final String RESERVED = "[\\x3B\\x2F\\x3F\\x3A\\x40\\x26\\x3D]"; //;|/|?|:|@|&|=
private static final String UNRESERVED = "[" + ALPHA + DIGIT + SAFE + EXTRA + "]";
private static final String UCHAR = "(?:" + UNRESERVED + "|" + ESCAPE + ")";
private static final String XCHAR = "(?:" + UNRESERVED + "|" + RESERVED + "|" + ESCAPE + ")";
private static final String DIGITS = DIGIT + "+";
//Standard LDAP Attribute Definitions
private static final String ATTRIBUTE_SEPARATOR = ":";
private static final String OPTION_SEPARATOR = ";";
private static final String BASE64_INDICATOR = ":";
private static final String URL_INDICATOR = "<";
private static final String ATTRIBUTE_TYPE_CHARS = ALPHA + DIGIT + "-";
private static final String LDAP_OID = "[[0-9]|[1-9][0-9]+][\\.(?:[0-9]|[1-9][0-9]+)]+";
private static final String OPTION = "[" + ATTRIBUTE_TYPE_CHARS + "]+";
private static final String OPTIONS = "[" + OPTION_SEPARATOR + OPTION + "]*";
private static final String ATTRIBUTE_TYPE = LDAP_OID + "|" + ALPHANUM + "[" + ATTRIBUTE_TYPE_CHARS + "]*";
private static final String ATTRIBUTE_DESCRIPTION = "(" + ATTRIBUTE_TYPE + ")(" + OPTIONS + ")";
private static final String SAFE_CHAR = "[\\p{ASCII}&&[^\\x00\\x0A\\x0D]]"; //Any ASCII except NUL, LF, and CR
private static final String SAFE_INIT_CHAR = "[\\p{ASCII}&&[^ \\x00\\x0A\\x0D\\x3A\\x3C]]"; //Any ASCII except NUL, LF, CR, SPACE, colon, and less-than
private static final String SAFE_STRING = "(" + SAFE_INIT_CHAR + SAFE_CHAR + "*)";
private static final String FILL = "[ ]*"; //Any number of spaces
//BASE64 Definitions
private static final String BASE64_CHAR = "[\\x2B\\x2F\\x30-\\x39\\x3D\\x41-\\x5A\\x61-\\x7A]"; //+, /, 0-9, -, A-Z, a-z
private static final String BASE64_STRING = "(" + BASE64_CHAR + "*)";
//URL Components
private static final String USER = "[" + UCHAR + "\\x3B\\x3F\\x26\\x3D]*"; //UCHAR|;|?|&|=
private static final String PASSWORD = "[" + UCHAR + "\\x3B\\x3F\\x26\\x3D]*"; //UCHAR|;|?|&|=
private static final String DOMAINLABEL = ALPHANUM + "|" + ALPHANUM + "[" + ALPHANUM + "-]*" + ALPHANUM;
private static final String TOPLABEL = ALPHA + "|" + ALPHA + "[" + ALPHANUM + "-]*" + ALPHANUM;
private static final String HOSTNAME = "(?:" + DOMAINLABEL + "\\.)*" + TOPLABEL;
private static final String IPADDRESS = "(?:" + DIGIT + "{1,3}\\.){3}" + DIGIT + "{1,3}";
private static final String HOST = "(?:" + HOSTNAME + "|" + IPADDRESS + ")";
private static final String PORT = DIGITS;
private static final String HOSTPORT = HOST + "(?::" + PORT + ")?";
private static final String URLPATH = XCHAR + "*";
private static final String LOGIN = "(?:" + USER + "(?::" + PASSWORD + ")?@)?" + HOSTPORT;
//URL Definitions
private static final String SCHEME = "[" + LOW_ALPHA + DIGIT + "\\x2B\\x2D\\x2E]+";
private static final String IP_SCHEMEPART = "//" + LOGIN + "(?:/" + URLPATH + ")?";
private static final String SCHEMEPART = "(?:" + XCHAR + "*|" + IP_SCHEMEPART + ")";
private static final String GENERIC_URL = SCHEME + ":" + SCHEMEPART;
//HTTP Definition
private static final String HSEGMENT = "[" + UCHAR + "\\x3A\\x3B\\x26\\x3D\\x40]*"; //UCHAR|:|;|&|=|@
private static final String HPATH = HSEGMENT + "[/" + HSEGMENT + "]*";
private static final String SEARCH = HSEGMENT;
private static final String HTTP_URL = "http://" + HOSTPORT + "(?:/" + HPATH + "(?:\\x3F" + SEARCH + ")?)?";
//FTP
private static final String FSEGMENT = "[" + UCHAR + "\\x3F\\x3A\\x26\\x3D\\x40]*"; //UCHAR|?|:|&|=|@
private static final String FPATH = FSEGMENT + "[/" + FSEGMENT + "]*";
private static final String FTPTYPE = "[AIDaid]";
private static final String FTP_URL = "ftp://" + LOGIN + "(?:/" + FPATH + "(?:;type=" + FTPTYPE + ")?)?";
//NEWS
private static final String GROUP = ALPHA + "[" + ALPHA + DIGIT + "\\x2D\\x2E\\x2B\\x5F]*"; //ALPHA [ALPHA|DIGIT|-|.|+|_]*
private static final String ARTICLE = "[" + UCHAR + "\\x3A\\x3B\\x2F\\x3F\\x26\\x3D]@" + HOST; //[UCHAR|;|/|?|:|&|=]@HOST
private static final String GROUPPART = "(?:\\x2A|" + GROUP + "|" + ARTICLE + ")";
private static final String NEWS_URL = "news:" + GROUPPART;
//NNTP
private static final String NNTP_URL = "nntp://" + HOSTPORT + "/" + GROUP + "/" + DIGITS;
//TELNET
private static final String TELNET_URL = "telnet://" + LOGIN + "[/]?";
//GOPHER
private static final String GTYPE = XCHAR;
private static final String SELECTOR = XCHAR + "*";
private static final String GOPHER_STRING = XCHAR + "*";
private static final String GOPHER_URL = "gopher://" + HOSTPORT + "(?:/(?:" + GTYPE + "(?:" + SELECTOR + "(?:%09" + SEARCH + "(?:%09" + GOPHER_STRING + ")?)?)?)?)?";
//WAIS
private static final String WPATH = UCHAR + "*";
private static final String WTYPE = UCHAR + "*";
private static final String DATABASE = UCHAR + "*";
private static final String WAIS_DOC = "wais://" + HOSTPORT + "/" + DATABASE + "/" + WTYPE + "/" + WPATH;
private static final String WAIS_INDEX = "wais://" + HOSTPORT + "/" + DATABASE + "\\?" + SEARCH;
private static final String WAIS_DATABASE = "wais://" + HOSTPORT + "/" + DATABASE;
private static final String WAIS_URL = WAIS_DATABASE + "|" + WAIS_INDEX + "|" + WAIS_DOC;
//MAILTO
private static final String ENCODED_822_ADDR = XCHAR + "+";
private static final String MAILTO_URL = "mailto:" + ENCODED_822_ADDR;
//FILE
private static final String FILE_URL = "file://(?:" + HOST + "|localhost)?/" + FPATH ;
//PROPERO
private static final String FIELD_VALUE = "[" + UCHAR + "\\x3F\\x3A\\x40\\x26]*"; //[UCHAR|?|:|@|&]*
private static final String FIELD_NAME = "[" + UCHAR + "\\x3F\\x3A\\x40\\x26]*"; //[UCHAR|?|:|@|&]*
private static final String FIELD_SPEC = ";" + FIELD_NAME + "=" + FIELD_VALUE;
private static final String PSEGMENT = "[" + UCHAR + "\\x3F\\x3A\\x40\\x26\\x3D]*"; //[UCHAR|?|:|@|&|=]*
private static final String PPATH = PSEGMENT + "(?:/" + PSEGMENT + ")*";
private static final String PROSPERO_URL = "prospero://" + HOSTPORT + "/" + PPATH + "(?:" + FIELD_SPEC + ")*";
//GENERIC
private static final String OTHER_URL = GENERIC_URL;
private static final String URL = "((?:" + HTTP_URL + ")|(?:" + FTP_URL + ")|(?:" + NEWS_URL + ")|(?:" + NNTP_URL + ")|(?:" + TELNET_URL + ")|(?:" + GOPHER_URL + ")|(?:" + WAIS_URL + ")|(?:" + MAILTO_URL + ")|(?:" + FILE_URL + ")|(?:" + PROSPERO_URL + ")|(?:" + OTHER_URL + "))"; //URL Pattern
//Expression Definitions
private static final String ATTRIBUTE_EXPRESSION = "^" + ATTRIBUTE_DESCRIPTION + ATTRIBUTE_SEPARATOR + FILL + SAFE_STRING + "{0,1}$"; //Regular Attribute
private static final String BASE64_ATTRIBUTE_EXPRESSION = "^" + ATTRIBUTE_DESCRIPTION + ATTRIBUTE_SEPARATOR + BASE64_INDICATOR + FILL + BASE64_STRING + "$"; //Base 64
private static final String URL_ATTRIBUTE_EXPRESSION = "^" + ATTRIBUTE_DESCRIPTION + ATTRIBUTE_SEPARATOR + URL_INDICATOR + FILL + URL + "$"; //URL
//Pattern Declarations
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile(ATTRIBUTE_EXPRESSION);
private static final Pattern BASE64_ATTRIBUTE_PATTERN = Pattern.compile(BASE64_ATTRIBUTE_EXPRESSION);
private static final Pattern URL_ATTRIBUTE_PATTERN = Pattern.compile(URL_ATTRIBUTE_EXPRESSION);
private boolean ordered = false;
/**
* Default constructor.
*/
public DefaultAttributeValidationPolicy() {
}
/**
* Constructor for indicating whether or not attribute values should be ordered alphabetically.
*
* @param ordered {@link java.lang.boolean} value.
*/
public DefaultAttributeValidationPolicy(boolean ordered) {
this.ordered = ordered;
}
/**
* Indicates whether or not the attribute values should be ordered alphabetically.
*
* @param ordered {@link java.lang.boolean} value.
*/
public void setOrdered(boolean ordered) {
this.ordered = ordered;
}
/**
* Validates attribute contained in the buffer and returns an LdapAttribute.
* <p>
* Ensures attributes meets one of three prescribed patterns for valid attributes:
* <ol>
* <li>A standard attribute pattern of the form: ATTR_ID[;options]: VALUE</li>
* <li>A Base64 attribute pattern of the form: ATTR_ID[;options]:: BASE64_VALUE</li>
* <li>A url attribute pattern of the form: ATTR_ID[;options]:< URL_VALUE</li>
* </ol>
* <p>
* Upon success an LdapAttribute object is returned.
*
* @param buffer {@inheritDoc}
* @return {@inheritDoc}
* @throws InvalidAttributeFormatException if the attribute does not meet one of the three patterns above
* or the attribute cannot be parsed.
*/
public Attribute parse(String buffer) {
log.trace("Parsing --> [" + buffer + "]");
Matcher matcher = ATTRIBUTE_PATTERN.matcher(buffer);
if (matcher.matches()) {
//Is a regular attribute...
return parseStringAttribute(matcher);
}
matcher = BASE64_ATTRIBUTE_PATTERN.matcher(buffer);
if (matcher.matches()) {
//Is a base64 attribute...
return parseBase64Attribute(matcher);
}
matcher = URL_ATTRIBUTE_PATTERN.matcher(buffer);
if (matcher.matches()) {
//Is a URL attribute...
return parseUrlAttribute(matcher);
}
//default: no match.
throw new InvalidAttributeFormatException("Not a valid attribute: " + buffer);
}
private LdapAttribute parseStringAttribute(Matcher matcher) {
String id = matcher.group(1);
String value = matcher.group(3);
List<String> options = Arrays.asList(matcher.group(2).split(OPTION_SEPARATOR));
if (options.isEmpty()) {
return new LdapAttribute(id, value, ordered);
} else {
return new LdapAttribute(id, value, options, ordered);
}
}
private LdapAttribute parseBase64Attribute(Matcher matcher) {
try {
String id = matcher.group(1);
String value = matcher.group(3);
List<String> options = Arrays.asList(matcher.group(2).split(OPTION_SEPARATOR));
if (options.isEmpty()) {
return new LdapAttribute(id, new BASE64Decoder().decodeBuffer(value), ordered);
} else {
return new LdapAttribute(id, new BASE64Decoder().decodeBuffer(value), options, ordered);
}
} catch (IOException e) {
throw new InvalidAttributeFormatException(e);
}
}
private LdapAttribute parseUrlAttribute(Matcher matcher) {
try {
String id = matcher.group(1);
String value = matcher.group(3);
List<String> options = Arrays.asList(matcher.group(2).split(OPTION_SEPARATOR));
if (options.isEmpty()) {
return new LdapAttribute(id, new URI(value), ordered);
} else {
return new LdapAttribute(id, new URI(value), options, ordered);
}
} catch (URISyntaxException e) {
throw new InvalidAttributeFormatException(e);
}
}
}

View File

@@ -0,0 +1,54 @@
package org.springframework.ldap.ldif.support;
/**
* Enumeration declaring possible event types when parsing LDIF files.
*
* @author Keith Barlow
*/
public enum LineIdentifier {
/**
* Every LDIF file may optionally start with a version identifier of the form 'version: 1'.
*/
VersionIdentifier,
/**
* Signifies the start of a new record in the file has been encountered: a DN declaration.
*/
NewRecord,
/**
* Signals the end of record has been reached.
*/
EndOfRecord,
/**
* Signifies the event when a new attribute is encountered.
*/
Attribute,
/**
* Indicates the current line parsed is a continuation of the previous line.
*/
Continuation,
/**
* The current line is a comment and should be ignored.
*/
Comment,
/**
* An LDAP changetype control was encountered.
*/
Control,
/**
* Record being parsed is a 'changetype' record.
*/
ChangeType,
/**
* Parsed line should be ignored - used to skip remaining lines in a 'changetype' record.
*/
Void
}

View File

@@ -0,0 +1,103 @@
/**
* Policy object for enforcing LDIF record separation rules. Designed explicitly for use in LDIFParser.
*/
package org.springframework.ldap.ldif.support;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This default separator policy should really not be required to be
* replaced but it is modular just in case.
* <p>
* This class applies the separation policy prescribed in RFC2849
* for LDIF files and identifies the line type from the input.
*
* @author Keith Barlow
*
*/
public class SeparatorPolicy {
private static Log log = LogFactory.getLog(SeparatorPolicy.class);
/*
* Line Identification Patterns.
*/
private static final String VERSION_IDENTIFIER = "^version: [0-9]+(\\.[0-9]*){0,1}$";
private static final String CONTROL = "control:";
private static final String CHANGE_TYPE = "changetype:";
private static final String CONTINUATION = " ";
private static final String COMMENT = "#";
private static final String NewRecord = "^dn:.*$";
private boolean record = false;
private boolean skip = false;
public SeparatorPolicy() {
}
/**
* Assess a read line.
* <p>
* In LDIF, lines must adhere to a particular format. A line can only contain one attribute
* and its value. The value may span multiple lines. Continuation lines are marked by the presence
* of a single space in the 1st position. Non-continuation lines must start in the first position.
*
*/
public LineIdentifier assess(String line) {
log.trace("Assessing --> [" + line + "]");
if (record) {
if (StringUtils.isEmpty(line)) {
record = false;
skip = false;
return LineIdentifier.EndOfRecord;
} else if (skip) {
return LineIdentifier.Void;
} else {
if (line.startsWith(CONTROL)) {
skip = true;
return LineIdentifier.Control;
} else if (line.startsWith(CHANGE_TYPE)) {
skip = true;
return LineIdentifier.ChangeType;
} else if (line.startsWith(COMMENT)) {
return LineIdentifier.Comment;
} else if (line.startsWith(CONTINUATION)) {
return LineIdentifier.Continuation;
} else {
return LineIdentifier.Attribute;
}
}
} else {
if (StringUtils.isNotEmpty(line) && line.matches(VERSION_IDENTIFIER) && !skip) {
//Version Identifiers are ignored by parser.
return LineIdentifier.VersionIdentifier;
} else if (StringUtils.isNotEmpty(line) && line.matches(NewRecord)) {
record = true;
skip = false;
return LineIdentifier.NewRecord;
} else {
return LineIdentifier.Void;
}
}
}
}

View File

@@ -0,0 +1,10 @@
/**
* Provides the necessary auxiliary classes utilized by the LDIFParser.
* <p>
* Notable classes in this package include:
* <ul>
* <li>AttributeValidationPolicy - specifies the proper format valid attributes must adhere to.</li>
* <li>SeparatorPolicy - translates lines read from the resource into attributes.</li>
* </ul>
*/
package org.springframework.ldap.ldif.support;

View File

@@ -0,0 +1,66 @@
package org.springframework.ldap.schema;
import javax.naming.NamingException;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapRdn;
import org.springframework.ldap.ldif.LdapAttributes;
import sun.misc.BASE64Encoder;
/**
* DefaultSchemaSpecification establishes a minimal set of requirements for object classes.
* <p>
* The default specification, which does not validate against any schema, simply deems all
* objects valid as long as they have the following:
* <ul>
* <li>a non-null DN</li>
* <li>a matching naming attribute class definition.</li>
* <li>and an object</li>
* </ul>
*
* @author Keith Barlow
*
*/
public class DefaultSchemaSpecification implements Specification<LdapAttributes> {
/**
* Determines if the policy is satisfied by the supplied LdapAttributes object.
*
* @throws NamingException
*/
public boolean isSatisfiedBy(LdapAttributes record) throws NamingException {
if (record != null) {
//DN is required.
DistinguishedName dn = record.getDN();
if (dn != null) {
//objectclass definition is required.
if (record.get("objectclass") != null) {
//Naming attribute is required.
LdapRdn rdn = dn.getLdapRdn(dn.size() - 1);
if (record.get(rdn.getKey()) != null) {
Object object = record.get(rdn.getKey()).get();
if (object instanceof String) {
String value = (String) record.get(rdn.getKey()).get();
if (rdn.getValue().equalsIgnoreCase(value)) {
return true;
}
} else if(object instanceof byte[]) {
BASE64Encoder encoder = new BASE64Encoder();
String rdnValue = encoder.encode(rdn.getValue().getBytes());
String attributeValue = encoder.encode((byte[]) object);
if (rdnValue.equals(attributeValue)) return true;
}
}
}
}
}
return false;
}
}

View File

@@ -0,0 +1,18 @@
package org.springframework.ldap.schema;
import javax.naming.NamingException;
/**
* The specification interface is implemented to declare rules that
* a record must conform to. The motiviation behind this class was
* to provide a mechanism to enable schema validations.
*
* @author Keith Barlow
*
* @param <T>
*/
public interface Specification<T> {
boolean isSatisfiedBy(T record) throws NamingException;
}

View File

@@ -0,0 +1,7 @@
/**
* This package is aimed at providing a mechanism to implement LDAP schemas.
* <p>
* Utilized by the LDIFParser to validate object composition post assembly, these
* classes may also be referenced by other utilities where seen fit.
*/
package org.springframework.ldap.schema;

View File

@@ -0,0 +1,143 @@
package org.springframework.ldap.ldif;
import static org.junit.Assert.*;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized;
import org.springframework.ldap.ldif.LdapAttribute;
import org.springframework.ldap.ldif.support.DefaultAttributeValidationPolicy;
import sun.misc.BASE64Decoder;
/**
* Parses a preselected set of attributes to test the full spectrum of functionality
* expected of an attribute parser. Attributes are validated to ensure they conform to
* the requirements for attribute values prescribed in RFC2849.
*
* @author Keith Barlow
*
*/
@RunWith(Parameterized.class)
public class DefaultAttributeValidationPolicyTest {
private static Log log = LogFactory.getLog(DefaultAttributeValidationPolicyTest.class);
private static DefaultAttributeValidationPolicy policy = new DefaultAttributeValidationPolicy();
private static enum AttributeType { STRING, BASE64, URL }
private String line;
private String id;
private String options;
private String value;
private AttributeType type;
/**
* The data set to parse.
* @return
*/
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
//Format: line, id, options, value, type
//String
{ "cn: Keith Barlow", "cn", "", "Keith Barlow", AttributeType.STRING},
{ "sn: Jensen", "sn", "", "Jensen", AttributeType.STRING},
{ "cn: Barbara J Jensen", "cn", "", "Barbara J Jensen", AttributeType.STRING},
{ "telephonenumber: +1 408 555 1212", "telephonenumber", "", "+1 408 555 1212", AttributeType.STRING},
{ "description: A big sailing fan.", "description", "", "A big sailing fan.", AttributeType.STRING},
{ "title;lang-en;phonetic: Sales, Director", "title", ";lang-en;phonetic", "Sales, Director", AttributeType.STRING},
{ "mail: rogasawara@airius.co.jp", "mail", "", "rogasawara@airius.co.jp", AttributeType.STRING},
{ "description: A big sailing fan.", "description", "", "A big sailing fan.", AttributeType.STRING},
//Base64
{ "xml:: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=", "xml", "", "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=", AttributeType.BASE64},
{ "ou;lang-ja;phonetic:: 44GI44GE44GO44KH44GG44G2", "ou", ";lang-ja;phonetic", "44GI44GE44GO44KH44GG44G2", AttributeType.BASE64 },
{ "dn:: dWlkPXJvZ2FzYXdhcmEsb3U95Za25qWt6YOoLG89QWlyaXVz", "dn", "", "dWlkPXJvZ2FzYXdhcmEsb3U95Za25qWt6YOoLG89QWlyaXVz", AttributeType.BASE64 },
{ "cn;lang-ja:: 5bCP56yg5Y6fIOODreODieODi+ODvA==", "cn", ";lang-ja", "5bCP56yg5Y6fIOODreODieODi+ODvA==", AttributeType.BASE64 },
//Url
{ "url:< http://www.oracle.com/", "url", "", "http://www.oracle.com/", AttributeType.URL},
{ "url:< http://java.sun.com/j2se/1.3/docs/guide/collections/designfaq.html", "url", "", "http://java.sun.com/j2se/1.3/docs/guide/collections/designfaq.html", AttributeType.URL},
{ "url:< ftp://kbarlow:test@ftp.is.co.za/rfc/rfc1808.txt", "url", "", "ftp://kbarlow:test@ftp.is.co.za/rfc/rfc1808.txt", AttributeType.URL},
{ "url:< ftp://ftp.is.co.za:2100/rfc/rfc1808.txt;type=a", "url", "", "ftp://ftp.is.co.za:2100/rfc/rfc1808.txt;type=a", AttributeType.URL},
{ "url:< telnet://kbarlow@melvyl.ucop.edu/", "url", "", "telnet://kbarlow@melvyl.ucop.edu/", AttributeType.URL},
{ "url:< telnet://kbarlow:test@melvyl.ucop.edu/", "url", "", "telnet://kbarlow:test@melvyl.ucop.edu/", AttributeType.URL},
{ "url:< gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles", "url", "", "gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles", AttributeType.URL},
{ "url:< file:///usr/local/directory/photos/fiona.jpg", "url", "", "file:///usr/local/directory/photos/fiona.jpg", AttributeType.URL},
{ "url:< mailto:java-net@java.sun.com", "url", "", "mailto:java-net@java.sun.com", AttributeType.URL},
{ "url:< news:comp.infosystems.www.servers.unix", "url", "", "news:comp.infosystems.www.servers.unix", AttributeType.URL},
{ "url:< prospero://host.dom:1525//pros/name;key=value", "url", "", "prospero://host.dom:1525//pros/name;key=value", AttributeType.URL},
{ "url:< nntp://news.cs.hut.fi/alt.html/239157", "url", "", "nntp://news.cs.hut.fi/alt.html/239157", AttributeType.URL},
{ "url:< wais://vega.lib.ncsu.edu/alawon.src?nren", "url", "", "wais://vega.lib.ncsu.edu/alawon.src?nren", AttributeType.URL}
});
}
/**
* DefaultAttributeValidationPolicyTest: Parameterized constructor.
* @param line The attribute to parse.
* @param id The ID portion of the attribute expected on successful parsing.
* @param options The Options expected on successful parsing.
* @param value The value expected from successful parsing.
* @param type The attribute type: one of enum AttributeType.
*/
public DefaultAttributeValidationPolicyTest(String line, String id, String options, String value, AttributeType type) {
this.line = line;
this.id = id;
this.options = options;
this.value = value;
this.type = type;
}
/**
* The test case: parses passed in parameters and validates the outcome against the expected results.
*/
@Test
public void parseAttribute() {
try {
LdapAttribute attribute = (LdapAttribute) policy.parse(line);
assertTrue("IDs do not match: [expected: " + attribute.getID() + ", obtained: " + id + "]", id.equalsIgnoreCase(attribute.getID()));
String[] expected = options.split(";");
Arrays.sort(expected);
String[] obtained = attribute.getOptions().toArray(new String[] {});
Arrays.sort(obtained);
assertArrayEquals("Options do not match: ", expected, obtained);
switch(type) {
case STRING:
assertTrue("Value is not a string.", attribute.get() instanceof String);
assertEquals("Values do not match: ", value, (String) attribute.get());
break;
case BASE64:
byte[] bytes = new BASE64Decoder().decodeBuffer(value);
assertTrue("Value is not a byte[].", attribute.get() instanceof byte[]);
assertArrayEquals("Values do not match: ", bytes, (byte[]) attribute.get());
break;
case URL:
URI url = new URI(value);
assertTrue("Value is not a URL.", attribute.get() instanceof URI);
assertEquals("Values do not match: ", url, (URI) attribute.get());
break;
}
log.info("Success!");
} catch (Exception e) {
fail("Exception thrown: " + e.getClass().getSimpleName() + " (message: " + e.getMessage() + ")");
}
}
}

View File

@@ -0,0 +1,100 @@
package org.springframework.ldap.ldif;
import static org.junit.Assert.*;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.ldif.LdapAttributes;
import org.springframework.ldap.ldif.parser.LDIFParser;
/**
* Unit test for LDIFParser.
*
* Test results in complete end to end test of all LDIFParser functionality:
* 1.) Open a file
* 2.) Read lines and compose an attribute.
* 3.) Parse the attribute and create a LdapAttribute object.
* 4.) Repeat until end of record (Identify end of record).
* 5.) Return a valid LdapAttributes object.
* 6.) Close file upon completion.
*
* Provided test file is comprised of sample LDIFs from RFC2849 and exhausts the full range of
* the functionality prescribed by RFC2849 for the LDAP Data Interchange Format (LDIF).
*
* @author Keith Barlow
*
*/
public class LDIFParserTest {
private static Log log = LogFactory.getLog(LDIFParserTest.class);
private LDIFParser parser;
/**
* Default constructor: loads a preselected resource with sample LDIF entries.
* Each entry is parsed and checked for a DN and objectclass. Output is printed for visual verification
* of LDIF correctness.
*/
public LDIFParserTest() {
parser = new LDIFParser(new ClassPathResource("test.ldif"));
}
/**
* Setup: opens file.
*/
@Before
public void openLdif() {
try {
parser.open();
} catch (IOException e) {
fail(e.getMessage());
}
}
/**
* Executes test: reads all records from LDIF file and validates an LdapAttributes object is successfully created.
*/
@Test
public void parseLdif() {
try {
LdapAttributes attributes;
int count = 0;
while (parser.hasMoreRecords()) {
attributes = parser.getRecord();
log.info("attributes:\n" + attributes);
if (attributes != null) {
assertTrue("A dn is required.", attributes.getDN() != null);
assertTrue("Object class is required.", attributes.get("objectclass") != null);
count++;
}
log.debug("hasMoreRecords: " + parser.hasMoreRecords());
}
log.info("record count: " + count);
log.info("Done!");
} catch (IOException e) {
fail(e.getMessage());
}
}
/**
* Cleanup: closes file.
*/
@After
public void closeLdif() {
try {
parser.close();
} catch (IOException e) {
fail(e.getMessage());
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* This package provides unit classes to test the validity of other classes in this suite.
*/
package org.springframework.ldap.ldif;

View File

@@ -0,0 +1,225 @@
version: 1
#
#Examples of LDAP Data Interchange Format
#
#
#Example 1: An simple LDAP file with two entries
#
dn: cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
cn: Barbara Jensen
cn: Barbara J Jensen
cn: Babs Jensen
sn: Jensen
uid: bjensen
telephonenumber: +1 408 555 1212
description: A big sailing fan.
dn: cn=Bjorn Jensen, ou=Accounting, dc=airius, dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
cn: Bjorn Jensen
sn: Jensen
telephonenumber: +1 408 555 1212
#
# Example 2: A file containing an entry with a folded attribute value
#
dn:cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com
objectclass:top
objectclass:person
objectclass:organizationalPerson
cn:Barbara Jensen
cn:Barbara J Jensen
cn:Babs Jensen
sn:Jensen
uid:bjensen
telephonenumber:+1 408 555 1212
description:Babs is a big sailing fan, and travels extensively in sea
rch of perfect sailing conditions.
title:Product Manager, Rod and Reel Division
#
# Example 3: A file containing a base-64-encoded value
#
dn: cn=Gern Jensen, ou=Product Testing, dc=airius, dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
cn: Gern Jensen
cn: Gern O Jensen
sn: Jensen
uid: gernj
telephonenumber: +1 408 555 1212
description:: V2hhdCBhIGNhcmVmdWwgcmVhZGVyIHlvdSBhcmUhICBUaGlzIHZhbHVl
IGlzIGJhc2UtNjQtZW5jb2RlZCBiZWNhdXNlIGl0IGhhcyBhIGNvbnRyb2wgY2hhcmFjdG
VyIGluIGl0IChhIENSKS4NICBCeSB0aGUgd2F5LCB5b3Ugc2hvdWxkIHJlYWxseSBnZXQg
b3V0IG1vcmUu
#
# Example 4: A file containing an entries with UTF-8-encoded attribute
# values, including language tags. Comments indicate the contents
# of UTF-8-encoded attributes and distinguished names.
#
dn:: b3U95Za25qWt6YOoLG89QWlyaXVz
# dn:: ou=<JapaneseOU>,o=Airius
objectclass: top
objectclass: organizationalUnit
ou:: 5Za25qWt6YOo
# ou:: <JapaneseOU>
ou;lang-ja:: 5Za25qWt6YOo
# ou;lang-ja:: <JapaneseOU>
ou;lang-ja;phonetic:: 44GI44GE44GO44KH44GG44G2
# ou;lang-ja:: <JapaneseOU_in_phonetic_representation>
ou;lang-en: Sales
description: Japanese office
dn:: dWlkPXJvZ2FzYXdhcmEsb3U95Za25qWt6YOoLG89QWlyaXVz
# dn:: uid=<uid>,ou=<JapaneseOU>,o=Airius
userpassword: {SHA}O3HSv1MusyL4kTjP+HKI5uxuNoM=
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: rogasawara
mail: rogasawara@airius.co.jp
givenname;lang-ja:: 44Ot44OJ44OL44O8
# givenname;lang-ja:: <JapaneseGivenname>
sn;lang-ja:: 5bCP56yg5Y6f
# sn;lang-ja:: <JapaneseSn>
cn;lang-ja:: 5bCP56yg5Y6fIOODreODieODi+ODvA==
# cn;lang-ja:: <JapaneseCn>
title;lang-ja:: 5Za25qWt6YOoIOmDqOmVtw==
# title;lang-ja:: <JapaneseTitle>
preferredlanguage: ja
givenname:: 44Ot44OJ44OL44O8
# givenname:: <JapaneseGivenname>
sn:: 5bCP56yg5Y6f
# sn:: <JapaneseSn>
cn:: 5bCP56yg5Y6fIOODreODieODi+ODvA==
# cn:: <JapaneseCn>
title:: 5Za25qWt6YOoIOmDqOmVtw==
# title:: <JapaneseTitle>
givenname;lang-ja;phonetic:: 44KN44Gp44Gr44O8
# givenname;lang-ja;phonetic::
# <JapaneseGivenname_in_phonetic_representation_kana>
sn;lang-ja;phonetic:: 44GK44GM44GV44KP44KJ
# sn;lang-ja;phonetic:: <JapaneseSn_in_phonetic_representation_kana>
cn;lang-ja;phonetic:: 44GK44GM44GV44KP44KJIOOCjeOBqeOBq+ODvA==
# cn;lang-ja;phonetic:: <JapaneseCn_in_phonetic_representation_kana>
title;lang-ja;phonetic:: 44GI44GE44GO44KH44GG44G2IOOBtuOBoeOCh+OBhg==
# title;lang-ja;phonetic::
# <JapaneseTitle_in_phonetic_representation_kana>
givenname;lang-en: Rodney
sn;lang-en: Ogasawara
cn;lang-en: Rodney Ogasawara
title;lang-en: Sales, Director
#
# Example 5: A file containing a reference to an external file
#
dn: cn=Horatio Jensen, ou=Product Testing, dc=airius, dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
cn: Horatio Jensen
cn: Horatio N Jensen
sn: Jensen
uid: hjensen
telephonenumber: +1 408 555 1212
jpegphoto:< file:///usr/local/directory/photos/hjensen.jpg
#
# Example 6: A file containing a series of change records and comments
#
# Add a new entry
dn: cn=Fiona Jensen, ou=Marketing, dc=airius, dc=com
changetype: add
objectclass: top
objectclass: person
objectclass: organizationalPerson
cn: Fiona Jensen
sn: Jensen
uid: fiona
telephonenumber: +1 408 555 1212
jpegphoto:< file:///usr/local/directory/photos/fiona.jpg
# Delete an existing entry
dn: cn=Robert Jensen, ou=Marketing, dc=airius, dc=com
changetype: delete
# Modify an entry's relative distinguished name
dn: cn=Paul Jensen, ou=Product Development, dc=airius, dc=com
changetype: modrdn
newrdn: cn=Paula Jensen
deleteoldrdn: 1
# Rename an entry and move all of its children to a new location in
# the directory tree (only implemented by LDAPv3 servers).
dn: ou=PD Accountants, ou=Product Development, dc=airius, dc=com
changetype: modrdn
newrdn: ou=Product Development Accountants
deleteoldrdn: 0
newsuperior: ou=Accounting, dc=airius, dc=com
# Modify an entry: add an additional value to the postaladdress
# attribute, completely delete the description attribute, replace
# the telephonenumber attribute with two values, and delete a specific
# value from the facsimiletelephonenumber attribute
dn: cn=Paula Jensen, ou=Product Development, dc=airius, dc=com
changetype: modify
add: postaladdress
postaladdress: 123 Anystreet $ Sunnyvale, CA $ 94086
-
delete: description
-
replace: telephonenumber
telephonenumber: +1 408 555 1234
telephonenumber: +1 408 555 5678
-
delete: facsimiletelephonenumber
facsimiletelephonenumber: +1 408 555 9876
-
# Modify an entry: replace the postaladdress attribute with an empty
# set of values (which will cause the attribute to be removed), and
# delete the entire description attribute. Note that the first will
# always succeed, while the second will only succeed if at least
# one value for the description attribute is present.
dn: cn=Ingrid Jensen, ou=Product Support, dc=airius, dc=com
changetype: modify
replace: postaladdress
-
delete: description
-
#
# Example 7: An LDIF file containing a change record with a control
#
# Delete an entry. The operation will attach the LDAPv3
# Tree Delete Control defined in [9]. The criticality
# field is "true" and the controlValue field is
# absent, as required by [9].
dn: ou=Product Development, dc=airius, dc=com
control: 1.2.840.113556.1.4.805 true
changetype: delete