Java tutorial
/* * Copyright 2005-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.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.beans.factory.InitializingBean; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapAttributes; import org.springframework.ldap.ldif.InvalidRecordFormatException; 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 LdifParser 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.core.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> * <i>NOTE: By default, objects are not validated. If validation is required, * an appropriate specification object must be set.</i> * <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. * <p> * Objects implementing the {@link javax.naming.directory.Attributes Attributes} interface are required to support a case sensitivity setting * which controls whether or not the attribute IDs of the object are case sensitive. The {@link #caseInsensitive caseInsensitive} * setting of the {@link LdifParser LdifParser} is passed to the constructor of any {@link javax.naming.directory.Attributes Attributes} created. The * default value for this setting is true so that case insensitive objects are created. * * @author Keith Barlow * */ public class LdifParser implements Parser, InitializingBean { 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> specification = new DefaultSchemaSpecification(); /** * This setting is used to control the case sensitivity of LdapAttribute objects returned by the parser. */ private boolean caseInsensitive = true; /** * Default constructor. */ public LdifParser() { } /** * Creates a LdifParser with the indicated case sensitivity setting. * * @param caseInsensitive Case sensitivity setting for LdapAttributes objects returned by the parser. */ public LdifParser(boolean caseInsensitive) { this.caseInsensitive = caseInsensitive; } /** * Creates an LdifParser for the specified resource with the provided case sensitivity setting. * * @param resource The resource to parse. * @param caseInsensitive Case sensitivity setting for LdapAttributes objects returned by the parser. */ public LdifParser(Resource resource, boolean caseInsensitive) { this.resource = resource; this.caseInsensitive = caseInsensitive; } /** * 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.specification = specification; } public void setResource(Resource resource) { this.resource = resource; } public void setCaseInsensitive(boolean caseInsensitive) { this.caseInsensitive = caseInsensitive; } public void open() throws IOException { Assert.notNull(resource, "Resource must be set."); reader = new BufferedReader(new InputStreamReader(resource.getInputStream())); } public boolean isReady() throws IOException { return reader.ready(); } 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()) { log.debug("Reader not ready!"); return null; } LdapAttributes record = new LdapAttributes(caseInsensitive); 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); if (specification.isSatisfiedBy(record)) { log.debug("record parsed:\n" + record); return record; } else { throw new InvalidRecordFormatException( "Record [dn: " + record.getDN() + "] 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); } } public void afterPropertiesSet() throws Exception { Assert.notNull(resource, "A resource to parse is required."); Assert.isTrue(resource.exists(), resource.getDescription() + ": resource does not exist!"); Assert.isTrue(resource.isReadable(), "Resource is not readable."); } }