View Javadoc
1   /*
2    * Nuiton Utils %%Ignore-License
3    *
4    *
5    * $HeadURL$
6    * 
7    * Licensed to the Apache Software Foundation (ASF) under one
8    * or more contributor license agreements. See the NOTICE file
9    * distributed with this work for additional information
10   * regarding copyright ownership. The ASF licenses this file
11   * to you under the Apache License, Version 2.0 (the
12   * "License"); you may not use this file except in compliance
13   * with the License. You may obtain a copy of the License at
14   *
15   * http://www.apache.org/licenses/LICENSE-2.0
16   *
17   * Unless required by applicable law or agreed to in writing,
18   * software distributed under the License is distributed on an
19   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20   * KIND, either express or implied. See the License for the
21   * specific language governing permissions and limitations
22   * under the License.
23   */
24  
25  /*
26  * Modified by Landais Gabriel, Code Lutin 2008
27  *
28  * Works with standard org.w3c.dom XML classes
29  *
30  */
31  
32  package org.nuiton.util;
33  
34  import org.w3c.dom.Attr;
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.Node;
38  import org.w3c.dom.NodeList;
39  import org.w3c.dom.ProcessingInstruction;
40  import org.w3c.dom.Text;
41  
42  import java.io.ByteArrayOutputStream;
43  import java.io.DataOutputStream;
44  import java.io.IOException;
45  import java.io.UnsupportedEncodingException;
46  import java.security.MessageDigest;
47  import java.security.NoSuchAlgorithmException;
48  import java.util.ArrayList;
49  import java.util.Arrays;
50  import java.util.Collection;
51  import java.util.SortedMap;
52  import java.util.TreeMap;
53  
54  /**
55   * Helper class to provide the functionality of the digest value generation. This is an implementation of the DHASH
56   * algorithm on .
57   *
58   * TODO tchemit 2010-08-25 : This class is a nightmare ? we talk about digest mixed with dom nodes ?
59   * TODO  tchemit 2010-08-25 : Should have more to explain the purpose (javadoc, author, since...)  or (rename | split) this class.
60   */
61  public class DigestGenerator {
62      public static final String UNICODE_BIG_UNMARKED = "UnicodeBigUnmarked";
63  
64      /**
65       * This method is an overloaded method for the digest generation for Document
66       *
67       * @param document FIXME
68       * @param digestAlgorithm FIXME
69       * @return Returns a byte array representing the calculated digest
70       * @throws Exception FIXME
71       */
72      public byte[] getDigest(Document document, String digestAlgorithm)
73              throws Exception {
74          byte[] digest;
75          try {
76              MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
77              ByteArrayOutputStream baos = new ByteArrayOutputStream();
78              DataOutputStream dos = new DataOutputStream(baos);
79              dos.writeInt(9);
80              Collection childNodes = getValidElements(document);
81              dos.writeInt(childNodes.size());
82              for (Object childNode : childNodes) {
83                  Node node = (Node) childNode;
84                  if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
85                      dos.write(getDigest((ProcessingInstruction) node,
86                                          digestAlgorithm));
87                  } else if (node.getNodeType() == Node.ELEMENT_NODE) {
88                      dos.write(getDigest((Element) node, digestAlgorithm));
89                  }
90              }
91              dos.close();
92              md.update(baos.toByteArray());
93              digest = md.digest();
94          } catch (NoSuchAlgorithmException e) {
95              throw new Exception(e);
96          } catch (IOException e) {
97              throw new Exception(e);
98          }
99          return digest;
100     }
101 
102     /**
103      * This method is an overloaded method for the digest generation for Node
104      *
105      * @param node FIXME
106      * @param digestAlgorithm FIXME
107      * @return Returns a byte array representing the calculated digest value
108      * @throws Exception FIXME
109      */
110     public byte[] getDigest(Node node, String digestAlgorithm) throws Exception {
111         if (node.getNodeType() == Node.ELEMENT_NODE) {
112             return getDigest((Element) node, digestAlgorithm);
113         }
114         if (node.getNodeType() == Node.TEXT_NODE) {
115             return getDigest((Text) node, digestAlgorithm);
116         }
117         if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
118             return getDigest((ProcessingInstruction) node, digestAlgorithm);
119         }
120         return new byte[0];
121     }
122 
123     /**
124      * This method is an overloaded method for the digest generation for Element
125      *
126      * @param element FIXME
127      * @param digestAlgorithm FIXME
128      * @return Returns a byte array representing the calculated digest value
129      * @throws Exception FIXME
130      */
131     public byte[] getDigest(Element element, String digestAlgorithm)
132             throws Exception {
133         byte[] digest;
134         try {
135             MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
136             ByteArrayOutputStream baos = new ByteArrayOutputStream();
137             DataOutputStream dos = new DataOutputStream(baos);
138             dos.writeInt(1);
139             dos.write(getExpandedName(element).getBytes(UNICODE_BIG_UNMARKED));
140             dos.write((byte) 0);
141             dos.write((byte) 0);
142             Collection attrs = getAttributesWithoutNS(element);
143             dos.writeInt(attrs.size());
144             for (Object attr : attrs) {
145                 dos.write(getDigest((Attr) attr, digestAlgorithm));
146             }
147             Node node = element.getFirstChild();
148             // adjoining Texts are merged,
149             // there is no 0-length Text, and
150             // comment nodes are removed.
151             int length = element.getChildNodes().getLength();
152             dos.writeInt(length);
153             while (node != null) {
154                 dos.write(getDigest(node, digestAlgorithm));
155                 node = node.getNextSibling();
156             }
157             dos.close();
158             md.update(baos.toByteArray());
159             digest = md.digest();
160         } catch (NoSuchAlgorithmException e) {
161             throw new Exception(e);
162         } catch (IOException e) {
163             throw new Exception(e);
164         }
165         return digest;
166     }
167 
168     /**
169      * This method is an overloaded method for the digest generation for ProcessingInstruction
170      *
171      * @param pi FIXME
172      * @param digestAlgorithm FIXME
173      * @return Returns a byte array representing the calculated digest value
174      * @throws Exception FIXME
175      */
176     public byte[] getDigest(ProcessingInstruction pi, String digestAlgorithm)
177             throws Exception {
178         byte[] digest;
179         try {
180             MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
181             md.update((byte) 0);
182             md.update((byte) 0);
183             md.update((byte) 0);
184             md.update((byte) 7);
185             md.update(pi.getTarget().getBytes(UNICODE_BIG_UNMARKED));
186             md.update((byte) 0);
187             md.update((byte) 0);
188             md.update(pi.getNodeValue().getBytes(UNICODE_BIG_UNMARKED));
189             digest = md.digest();
190         } catch (NoSuchAlgorithmException e) {
191             throw new Exception(e);
192         } catch (UnsupportedEncodingException e) {
193             throw new Exception(e);
194         }
195         return digest;
196     }
197 
198     /**
199      * This method is an overloaded method for the digest generation for Attr
200      *
201      * @param attribute FIXME
202      * @param digestAlgorithm FIXME
203      * @return Returns a byte array representing the calculated digest value
204      * @throws Exception FIXME
205      */
206     public byte[] getDigest(Attr attribute, String digestAlgorithm)
207             throws Exception {
208         byte[] digest = new byte[0];
209         if (!(attribute.getLocalName().equals("xmlns") || attribute
210                 .getLocalName().startsWith("xmlns:"))) {
211             try {
212                 MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
213                 md.update((byte) 0);
214                 md.update((byte) 0);
215                 md.update((byte) 0);
216                 md.update((byte) 2);
217                 md.update(getExpandedName(attribute).getBytes(
218                         UNICODE_BIG_UNMARKED));
219                 md.update((byte) 0);
220                 md.update((byte) 0);
221                 md.update(attribute.getValue().getBytes(UNICODE_BIG_UNMARKED));
222                 digest = md.digest();
223             } catch (NoSuchAlgorithmException e) {
224                 throw new Exception(e);
225             } catch (UnsupportedEncodingException e) {
226                 throw new Exception(e);
227             }
228         }
229         return digest;
230     }
231 
232     /**
233      * This method is an overloaded method for the digest generation for Text
234      *
235      * @param text FIXME
236      * @param digestAlgorithm FIXME
237      * @return Returns a byte array representing the calculated digest value
238      * @throws Exception FIXME
239      */
240     public byte[] getDigest(Text text, String digestAlgorithm) throws Exception {
241         byte[] digest;
242         try {
243             MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
244             md.update((byte) 0);
245             md.update((byte) 0);
246             md.update((byte) 0);
247             md.update((byte) 3);
248             md.update(text.getTextContent().getBytes(UNICODE_BIG_UNMARKED));
249             digest = md.digest();
250         } catch (NoSuchAlgorithmException e) {
251             throw new Exception(e);
252         } catch (UnsupportedEncodingException e) {
253             throw new Exception(e);
254         }
255         return digest;
256     }
257 
258     /**
259      * This method is an overloaded method for getting the expanded name namespaceURI followed by the local name for
260      * Element
261      *
262      * @param element FIXME
263      * @return Returns the expanded name of Element
264      */
265     public String getExpandedName(Element element) {
266         return element.getNamespaceURI() + ":" + element.getLocalName();
267     }
268 
269     /**
270      * This method is an overloaded method for getting the expanded name namespaceURI followed by the local name for
271      * Attr
272      *
273      * @param attribute FIXME
274      * @return Returns the expanded name of the Attr
275      */
276     public String getExpandedName(Attr attribute) {
277         return attribute.getNamespaceURI() + ":" + attribute.getLocalName();
278     }
279 
280     /**
281      * Gets the collection of attributes which are none namespace declarations for an Element
282      *
283      * @param element FIXME
284      * @return Returns the collection of attributes which are none namespace declarations
285      */
286     public Collection getAttributesWithoutNS(Element element) {
287         SortedMap map = new TreeMap();
288         for (int i = 0; i < element.getAttributes().getLength(); i++) {
289             Attr attribute = (Attr) element.getAttributes().item(i);
290             if (!(attribute.getLocalName().equals("xmlns") || attribute
291                     .getLocalName().startsWith("xmlns:"))) {
292                 map.put(getExpandedName(attribute), attribute);
293             }
294         }
295         return map.values();
296     }
297 
298     /**
299      * Gets the valid element collection of an Document. Element and ProcessingInstruction only
300      *
301      * @param document FIXME
302      * @return Returns a collection of ProcessingInstructions and Elements
303      */
304     public Collection getValidElements(Document document) {
305         ArrayList list = new ArrayList();
306         NodeList childNodes = document.getChildNodes();
307         for (int i = 0; i < childNodes.getLength(); i++) {
308             Node node = childNodes.item(i);
309             if (node.getNodeType() == Node.ELEMENT_NODE
310                 || node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
311                 list.add(node);
312             }
313         }
314         return list;
315     }
316 
317     /**
318      * Gets the String representation of the byte array
319      *
320      * @param array FIXME
321      * @return Returns the String of the byte
322      */
323     public String getStringRepresentation(byte[] array) {
324         String str = "";
325         for (byte anArray : array) {
326             str += anArray;
327         }
328         return str;
329     }
330 
331     /**
332      * Compares two Nodes for the XML equality
333      *
334      * @param node FIXME
335      * @param comparingNode FIXME
336      * @param digestAlgorithm FIXME
337      * @return Returns true if the Node XML contents are equal
338      * @throws Exception FIXME
339      */
340     public boolean compareNode(Node node, Node comparingNode,
341                                String digestAlgorithm) throws Exception {
342         return Arrays.equals(getDigest(node, digestAlgorithm), getDigest(
343                 comparingNode, digestAlgorithm));
344     }
345 
346     /**
347      * Compares two Documents for the XML equality
348      *
349      * @param document FIXME
350      * @param comparingDocument FIXME
351      * @param digestAlgorithm FIXME
352      * @return Returns true if the Document XML content are equal
353      * @throws Exception FIXME
354      */
355     public boolean compareDocument(Document document,
356                                    Document comparingDocument, String digestAlgorithm)
357             throws Exception {
358         return Arrays.equals(getDigest(document, digestAlgorithm), getDigest(
359                 comparingDocument, digestAlgorithm));
360     }
361 
362     /**
363      * Compares two Attributes for the XML equality
364      *
365      * @param attribute FIXME
366      * @param comparingAttribute FIXME
367      * @param digestAlgorithm FIXME
368      * @return Returns true if the Document XML content are equal
369      * @throws Exception FIXME
370      */
371     public boolean compareAttribute(Attr attribute, Attr comparingAttribute,
372                                     String digestAlgorithm) throws Exception {
373         return Arrays.equals(getDigest(attribute, digestAlgorithm), getDigest(
374                 comparingAttribute, digestAlgorithm));
375     }
376 
377     /** String representing the MD5 digest algorithm */
378     public static final String md5DigestAlgorithm = "MD5";
379 
380     /** String representing the SHA digest algorithm */
381     public static final String shaDigestAlgorithm = "SHA";
382 
383     /** String representing the SHA1 digest algorithm */
384     public static final String sha1DigestAlgorithm = "SHA1";
385 }