View Javadoc
1   /*
2    * Copyright 2013-2016 Brian Thomas Matthews
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.btmatthews.maven.plugins.ldap.dsml;
18  
19  import com.btmatthews.maven.plugins.ldap.FormatReader;
20  import com.unboundid.ldap.sdk.Attribute;
21  import com.unboundid.ldif.LDIFAddChangeRecord;
22  import com.unboundid.ldif.LDIFChangeRecord;
23  import org.dom4j.Document;
24  import org.dom4j.DocumentException;
25  import org.dom4j.Node;
26  import org.dom4j.io.SAXReader;
27  import org.jaxen.JaxenException;
28  import org.jaxen.NamespaceContext;
29  import org.jaxen.SimpleNamespaceContext;
30  import org.jaxen.XPath;
31  import org.jaxen.dom4j.Dom4jXPath;
32  
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.util.*;
36  
37  /**
38   * A {@link FormatReader} that reads LDAP directory entries from a DSML file.
39   *
40   * @author <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
41   * @since 1.2.0
42   */
43  public final class DSMLFormatReader implements FormatReader {
44  
45      /**
46       * Iterates over the directory entries extracted from the .dsml file.
47       */
48      private final Iterator<Node> entryIterator;
49      /**
50       * The namespace context maps the dsml prefix to the http://www.dsml.org/DSML namespace.
51       */
52      private final NamespaceContext namespaceContext;
53      /**
54       * The {@link XPath} expression used to iterate through the object classes for the DSML entry.
55       */
56      private final XPath objectClassXPath;
57      /**
58       * The {@link XPath} expression used to iterate through the attribute for the DSML entry.
59       */
60      private final XPath attrXPath;
61      /**
62       * The {@link XPath} expression used to iterate through the values of each attribute for the DSML entry.
63       */
64      private final XPath attrValueXPath;
65  
66      /**
67       * Initialise the reader to read DSML entries from an underlying input stream.
68       *
69       * @param inputStream The underlying input stream.
70       * @throws DocumentException If there was a problem parsing the DSML file.
71       * @throws JaxenException    If there was a problem creating the {@link XPath} expressions.
72       * @throws IOException       If there was a problem reading the DSML file.
73       */
74      public DSMLFormatReader(final InputStream inputStream) throws DocumentException, IOException, JaxenException {
75          final Map<String, String> map = new HashMap<String, String>();
76          map.put("dsml", "http://www.dsml.org/DSML");
77          namespaceContext = new SimpleNamespaceContext(map);
78          final SAXReader reader = new SAXReader();
79          final Document document = reader.read(inputStream);
80          final XPath xpath = createXPath("/dsml[namespace-uri()='http://www.dsml.org/DSML']/dsml:directory-entries/dsml:entry");
81          objectClassXPath = createXPath("dsml:objectclass/dsml:oc-value");
82          attrXPath = createXPath("dsml:attr");
83          attrValueXPath = createXPath("dsml:value");
84          final List<Node> entries = (List<Node>) xpath.selectNodes(document);
85          entryIterator = entries.iterator();
86      }
87  
88      /**
89       * Read the next change record from the underlying input stream.
90       *
91       * @return The next change record or {@code null} if the end of the input stream has been reached.
92       */
93      public LDIFChangeRecord nextRecord() {
94          if (entryIterator.hasNext()) {
95              try {
96                  final Node entryNode = entryIterator.next();
97                  final String dn = entryNode.valueOf("@dn");
98                  final List<Attribute> attributes = new ArrayList<Attribute>();
99  
100                 final List<Node> objectClassList = (List<Node>) objectClassXPath.selectNodes(entryNode);
101                 final String[] objectClasses = new String[objectClassList.size()];
102                 for (int j = 0; j < objectClasses.length; ++j) {
103                     objectClasses[j] = objectClassList.get(j).getStringValue();
104                 }
105                 attributes.add(new Attribute("objectclass", objectClasses));
106                 for (final Node attributeNode : (List<Node>) attrXPath.selectNodes(entryNode)) {
107                     final String attributeName = attributeNode.valueOf("@name");
108                     final List<Node> attributeValueNodes = (List<Node>) attrValueXPath.selectNodes(attributeNode);
109                     switch (attributeValueNodes.size()) {
110                         case 0:
111                             break;
112                         case 1: {
113                             final String attributeValue = attributeValueNodes.get(0).getStringValue();
114                             attributes.add(new Attribute(attributeName, attributeValue));
115                             break;
116                         }
117                         default: {
118                             final String[] attributeValues = new String[attributeValueNodes.size()];
119                             for (int j = 0; j < attributeValueNodes.size(); ++j) {
120                                 attributeValues[j] = attributeValueNodes.get(j).getStringValue();
121                             }
122                             attributes.add(new Attribute(attributeName, attributeValues));
123                             break;
124                         }
125                     }
126                 }
127                 return new LDIFAddChangeRecord(dn, attributes);
128             } catch (final JaxenException e) {
129                 return null;
130             }
131         } else {
132             return null;
133         }
134     }
135 
136     /**
137      * Called to close {@link DSMLFormatReader}.
138      */
139     public void close() {
140     }
141 
142     /**
143      * Create a {@link XPath} for the expression {@code xpathString}.
144      *
145      * @param xpathString The expression.
146      * @return The {@link XPath}.
147      * @throws JaxenException If there was a problem parsing the {@code xpathString} expression.
148      */
149     private XPath createXPath(final String xpathString) throws JaxenException {
150         final XPath xpath = new Dom4jXPath(xpathString);
151         xpath.setNamespaceContext(namespaceContext);
152         return xpath;
153     }
154 }