View Javadoc
1   /**
2    *
3    * Copyright 2004 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.codehaus.plexus.archiver.jar;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.Reader;
24  import java.io.StringWriter;
25  import java.io.Writer;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Enumeration;
29  import java.util.Hashtable;
30  import java.util.Iterator;
31  import java.util.Locale;
32  import java.util.StringTokenizer;
33  import java.util.Vector;
34  import java.util.jar.Attributes;
35  
36  import org.codehaus.plexus.archiver.ArchiverException;
37  
38  /**
39   * Holds the data of a jar manifest.
40   * <p>
41   * Manifests are processed according to the
42   * <a href="http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html">Jar
43   * file specification.</a>
44   * Specifically, a manifest element consists of
45   * a set of attributes and sections. These sections in turn may contain
46   * attributes. Note in particular that this may result in manifest lines
47   * greater than 72 bytes (including line break) being wrapped and continued
48   * on the next line. If an application can not handle the continuation
49   * mechanism, it is a defect in the application, not this task.</p>
50   *
51   * @since Ant 1.4
52   */
53  public class Manifest extends java.util.jar.Manifest implements Iterable<String> {
54  
55      /**
56       * The Name Attribute is the first in a named section
57       */
58      private static final String ATTRIBUTE_NAME = ManifestConstants.ATTRIBUTE_NAME;
59  
60      /**
61       * The From Header is disallowed in a Manifest
62       */
63      private static final String ATTRIBUTE_FROM = ManifestConstants.ATTRIBUTE_FROM;
64  
65      /**
66       * Default Manifest version if one is not specified
67       */
68      private static final String DEFAULT_MANIFEST_VERSION = ManifestConstants.DEFAULT_MANIFEST_VERSION;
69  
70      /**
71       * The max length of a line in a Manifest
72       */
73      private static final int MAX_LINE_LENGTH = 72;
74  
75      /**
76       * Max length of a line section which is continued. Need to allow
77       * for the CRLF.
78       */
79      private static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2;
80  
81      /**
82       * The End-Of-Line marker in manifests
83       */
84      static final String EOL = "\r\n";
85  
86      public static class BaseAttribute {
87  
88          /**
89           * The attribute's name
90           */
91          protected String name = null;
92  
93          /**
94           * Get the Attribute's name
95           *
96           * @return the attribute's name.
97           */
98          public String getName() {
99              return name;
100         }
101 
102         @Override
103         public boolean equals(Object o) {
104             if (this == o) {
105                 return true;
106             }
107             if (!(o instanceof BaseAttribute)) {
108                 return false;
109             }
110 
111             BaseAttribute that = (BaseAttribute) o;
112 
113             return !(name != null ? !name.equals(that.name) : that.name != null);
114         }
115 
116         @Override
117         public int hashCode() {
118             return name != null ? name.hashCode() : 0;
119         }
120     }
121 
122     /**
123      * An attribute for the manifest.
124      * Those attributes that are not nested into a section will be added to the "Main" section.
125      */
126     public static class Attribute extends BaseAttribute implements Iterable<String> {
127 
128         /**
129          * The attribute's value
130          */
131         private Vector<String> values = new Vector<String>();
132 
133         /**
134          * For multivalued attributes, this is the index of the attribute
135          * currently being defined.
136          */
137         private int currentIndex = 0;
138 
139         /**
140          * Construct an empty attribute
141          */
142         public Attribute() {}
143 
144         /**
145          * Construct a manifest by specifying its name and value
146          *
147          * @param name the attribute's name
148          * @param value the Attribute's value
149          */
150         public Attribute(String name, String value) {
151             this.name = name;
152             setValue(value);
153         }
154 
155         @Override
156         public Iterator<String> iterator() {
157             return values.iterator();
158         }
159 
160         /**
161          * @see java.lang.Object#hashCode
162          */
163         @Override
164         public int hashCode() {
165             int hashCode = super.hashCode();
166             hashCode += values.hashCode();
167             return hashCode;
168         }
169 
170         /**
171          * @see java.lang.Object#equals
172          */
173         @Override
174         public boolean equals(Object rhs) {
175             if (super.equals(rhs)) {
176                 return false;
177             }
178             if (rhs == null || rhs.getClass() != getClass()) {
179                 return false;
180             }
181 
182             if (rhs == this) {
183                 return true;
184             }
185 
186             Attribute rhsAttribute = (Attribute) rhs;
187             String lhsKey = getKey();
188             String rhsKey = rhsAttribute.getKey();
189             //noinspection SimplifiableIfStatement,ConstantConditions
190             if ((lhsKey == null && rhsKey != null) || (lhsKey != null && rhsKey == null) || !lhsKey.equals(rhsKey)) {
191                 return false;
192             }
193 
194             return rhsAttribute.values != null && values.equals(rhsAttribute.values);
195         }
196 
197         /**
198          * Set the Attribute's name; required
199          *
200          * @param name the attribute's name
201          */
202         public void setName(String name) {
203             this.name = name;
204         }
205 
206         /**
207          * Get the attribute's Key - its name in lower case.
208          *
209          * @return the attribute's key.
210          */
211         public String getKey() {
212             return getKey(name);
213         }
214 
215         /**
216          * Get the key for the specified attribute name - its name in lower case.
217          *
218          * @return the attribute's key.
219          */
220         private static String getKey(String name) {
221             if (name == null) {
222                 return null;
223             }
224             return name.toLowerCase(Locale.ENGLISH);
225         }
226 
227         /**
228          * Set the Attribute's value; required
229          *
230          * @param value the attribute's value
231          */
232         public void setValue(String value) {
233             if (currentIndex >= values.size()) {
234                 values.addElement(value);
235                 currentIndex = values.size() - 1;
236             } else {
237                 values.setElementAt(value, currentIndex);
238             }
239         }
240 
241         /**
242          * Get the Attribute's value.
243          *
244          * @return the attribute's value.
245          */
246         public String getValue() {
247             if (values.size() == 0) {
248                 return null;
249             }
250 
251             String fullValue = "";
252             for (String value : values) {
253                 fullValue += value + " ";
254             }
255             return fullValue.trim();
256         }
257 
258         /**
259          * Add a new value to this attribute - making it multivalued.
260          *
261          * @param value the attribute's additional value
262          */
263         public void addValue(String value) {
264             currentIndex++;
265             setValue(value);
266         }
267 
268         /**
269          * Writes the attribute out to a writer.
270          *
271          * @param writer the Writer to which the attribute is written
272          *
273          * @throws IOException if the attribute value cannot be written
274          */
275         void write(Writer writer) throws IOException {
276             for (String value : values) {
277                 writeValue(writer, value);
278             }
279         }
280 
281         /**
282          * Write a single attribute value out. Should handle multiple lines of attribute value.
283          *
284          * @param writer the Writer to which the attribute is written
285          * @param value the attribute value
286          *
287          * @throws IOException if the attribute value cannot be written
288          */
289         private void writeValue(Writer writer, String value) throws IOException {
290             String nameValue = name + ": " + value;
291 
292             StringTokenizer tokenizer = new StringTokenizer(nameValue, "\n\r");
293 
294             String prefix = "";
295 
296             while (tokenizer.hasMoreTokens()) {
297                 writeLine(writer, prefix + tokenizer.nextToken());
298                 prefix = " ";
299             }
300         }
301 
302         /**
303          * Write a single Manifest line. Should handle more than 72 bytes of line
304          *
305          * @param writer the Writer to which the attribute is written
306          * @param line the manifest line to be written
307          *
308          * @throws java.io.IOException when Io excepts
309          */
310         private void writeLine(Writer writer, String line) throws IOException {
311             // Make sure we have at most 70 bytes in UTF-8 as specified excluding line break
312             while (line.getBytes("UTF-8").length > MAX_SECTION_LENGTH) {
313                 // Try to find a MAX_SECTION_LENGTH
314                 // Use the minimum because we operate on at most chars and not bytes here otherwise
315                 // if we have more bytes than chars we will die in an IndexOutOfBoundsException.
316                 int breakIndex = Math.min(line.length(), MAX_SECTION_LENGTH);
317                 String section = line.substring(0, breakIndex);
318                 while (section.getBytes("UTF-8").length > MAX_SECTION_LENGTH && breakIndex > 0) {
319                     breakIndex--;
320                     section = line.substring(0, breakIndex);
321                 }
322                 if (breakIndex == 0) {
323                     throw new IOException("Unable to write manifest line " + line);
324                 }
325                 writer.write(section + EOL);
326                 line = " " + line.substring(breakIndex);
327             }
328             writer.write(line + EOL);
329         }
330     }
331 
332     public class ExistingAttribute extends Attribute implements Iterable<String> {
333 
334         private final Attributes attributes;
335 
336         public ExistingAttribute(Attributes attributes, String name) {
337             this.attributes = attributes;
338             this.name = name;
339         }
340 
341         @Override
342         public Iterator<String> iterator() {
343             return getKeys(attributes).iterator();
344         }
345 
346         @Override
347         public void setName(String name) {
348             throw new UnsupportedOperationException("Cant do this");
349         }
350 
351         @Override
352         public String getKey() {
353             return name;
354         }
355 
356         @Override
357         public void setValue(String value) {
358             attributes.putValue(name, value);
359         }
360 
361         @Override
362         public String getValue() {
363             return attributes.getValue(name);
364         }
365 
366         @Override
367         public void addValue(String value) {
368             String value1 = getValue();
369             value1 = (value1 != null) ? " " + value : value;
370             setValue(value1);
371         }
372 
373         @Override
374         void write(Writer writer) throws IOException {
375             throw new UnsupportedOperationException("Cant do this");
376         }
377     }
378 
379     private static Collection<String> getKeys(Attributes attributes) {
380         Collection<String> result = new ArrayList<String>();
381         for (Object objectObjectEntry : attributes.keySet()) {
382             result.add(objectObjectEntry.toString());
383         }
384         return result;
385     }
386 
387     /**
388      * A manifest section - you can nest attribute elements into sections.
389      * A section consists of a set of attribute values,
390      * separated from other sections by a blank line.
391      */
392     public static class Section implements Iterable<String> {
393 
394         /**
395          * Warnings for this section
396          */
397         private Vector<String> warnings = new Vector<String>();
398 
399         /**
400          * The section's name if any. The main section in a
401          * manifest is unnamed.
402          */
403         private String name = null;
404 
405         /**
406          * The section's attributes.
407          */
408         private Hashtable<String, Attribute> attributes = new Hashtable<String, Attribute>();
409 
410         /**
411          * Index used to retain the attribute ordering
412          */
413         private Vector<String> attributeIndex = new Vector<String>();
414 
415         /**
416          * The name of the section; optional -default is the main section.
417          *
418          * @param name the section's name
419          */
420         public void setName(String name) {
421             this.name = name;
422         }
423 
424         /**
425          * Get the Section's name.
426          *
427          * @return the section's name.
428          */
429         public String getName() {
430             return name;
431         }
432 
433         @Override
434         public Iterator<String> iterator() {
435             return attributes.keySet().iterator();
436         }
437 
438         /**
439          * Get a attribute of the section
440          *
441          * @param attributeName the name of the attribute
442          *
443          * @return a Manifest.Attribute instance if the attribute is
444          * single-valued, otherwise a Vector of Manifest.Attribute
445          * instances.
446          */
447         public Attribute getAttribute(String attributeName) {
448             return attributes.get(attributeName.toLowerCase(Locale.ENGLISH));
449         }
450 
451         /**
452          * Add an attribute to the section.
453          *
454          * @param attribute the attribute to be added to the section
455          *
456          * @throws ManifestException if the attribute is not valid.
457          */
458         public void addConfiguredAttribute(Attribute attribute) throws ManifestException {
459             String check = addAttributeAndCheck(attribute);
460             if (check != null) {
461                 throw new ManifestException(
462                         "Specify the section name using " + "the \"name\" attribute of the <section> element rather "
463                                 + "than using a \"Name\" manifest attribute");
464             }
465         }
466 
467         /**
468          * Add an attribute to the section
469          *
470          * @param attribute the attribute to be added.
471          *
472          * @return the value of the attribute if it is a name
473          * attribute - null other wise
474          *
475          * @throws ManifestException if the attribute already
476          * exists in this section.
477          */
478         public String addAttributeAndCheck(Attribute attribute) throws ManifestException {
479             if (attribute.getName() == null || attribute.getValue() == null) {
480                 throw new ManifestException("Attributes must have name and value");
481             }
482             if (attribute.getKey().equalsIgnoreCase(ATTRIBUTE_NAME)) {
483                 warnings.addElement("\"" + ATTRIBUTE_NAME + "\" attributes "
484                         + "should not occur in the main section and must be the "
485                         + "first element in all other sections: \"" + attribute.getName() + ": " + attribute.getValue()
486                         + "\"");
487                 return attribute.getValue();
488             }
489 
490             if (attribute.getKey().startsWith(Attribute.getKey(ATTRIBUTE_FROM))) {
491                 warnings.addElement("Manifest attributes should not start " + "with \"" + ATTRIBUTE_FROM + "\" in \""
492                         + attribute.getName() + ": " + attribute.getValue() + "\"");
493             } else {
494                 // classpath attributes go into a vector
495                 String attributeKey = attribute.getKey();
496                 if (attributeKey.equalsIgnoreCase(ManifestConstants.ATTRIBUTE_CLASSPATH)) {
497                     Attribute classpathAttribute = attributes.get(attributeKey);
498 
499                     if (classpathAttribute == null) {
500                         storeAttribute(attribute);
501                     } else {
502                         warnings.addElement("Multiple Class-Path attributes " + "are supported but violate the Jar "
503                                 + "specification and may not be correctly "
504                                 + "processed in all environments");
505 
506                         for (String value : attribute) {
507                             classpathAttribute.addValue(value);
508                         }
509                     }
510                 } else if (attributes.containsKey(attributeKey)) {
511                     throw new ManifestException("The attribute \"" + attribute.getName() + "\" may not occur more "
512                             + "than once in the same section");
513                 } else {
514                     storeAttribute(attribute);
515                 }
516             }
517             return null;
518         }
519 
520         /**
521          * Store an attribute and update the index.
522          *
523          * @param attribute the attribute to be stored
524          */
525         protected void storeAttribute(Attribute attribute) {
526             if (attribute == null) {
527                 return;
528             }
529 
530             String attributeKey = attribute.getKey();
531             attributes.put(attributeKey, attribute);
532             if (!attributeIndex.contains(attributeKey)) {
533                 attributeIndex.addElement(attributeKey);
534             }
535         }
536 
537         /**
538          * Get the warnings for this section.
539          *
540          * @return an Enumeration of warning strings.
541          */
542         public Enumeration<String> getWarnings() {
543             return warnings.elements();
544         }
545 
546         /**
547          * @see java.lang.Object#hashCode
548          */
549         @Override
550         public int hashCode() {
551             int hashCode = 0;
552 
553             if (name != null) {
554                 hashCode += name.hashCode();
555             }
556 
557             hashCode += attributes.hashCode();
558             return hashCode;
559         }
560 
561         /**
562          * @see java.lang.Object#equals
563          */
564         @Override
565         public boolean equals(Object rhs) {
566             if (rhs == null || rhs.getClass() != getClass()) {
567                 return false;
568             }
569 
570             if (rhs == this) {
571                 return true;
572             }
573 
574             Section rhsSection = (Section) rhs;
575 
576             return rhsSection.attributes != null && attributes.equals(rhsSection.attributes);
577         }
578     }
579 
580     public class ExistingSection implements Iterable<String> {
581 
582         private final Attributes backingAttributes;
583 
584         private final String sectionName;
585 
586         public ExistingSection(Attributes backingAttributes, String sectionName) {
587             this.backingAttributes = backingAttributes;
588             this.sectionName = sectionName;
589         }
590 
591         @Override
592         public Iterator<String> iterator() {
593             return getKeys(backingAttributes).iterator();
594         }
595 
596         public ExistingAttribute getAttribute(String attributeName) {
597             Attributes.Name name = new Attributes.Name(attributeName);
598             return backingAttributes.containsKey(name) ? new ExistingAttribute(backingAttributes, attributeName) : null;
599         }
600 
601         public String getName() {
602             return sectionName;
603         }
604 
605         public String getAttributeValue(String attributeName) {
606             return backingAttributes.getValue(attributeName);
607         }
608 
609         public void removeAttribute(String attributeName) {
610             backingAttributes.remove(new Attributes.Name(attributeName));
611         }
612 
613         public void addConfiguredAttribute(Attribute attribute) throws ManifestException {
614             backingAttributes.putValue(attribute.getName(), attribute.getValue());
615         }
616 
617         public String addAttributeAndCheck(Attribute attribute) throws ManifestException {
618             return remap(backingAttributes, attribute);
619         }
620 
621         @Override
622         public int hashCode() {
623             return backingAttributes.hashCode();
624         }
625 
626         @Override
627         public boolean equals(Object rhs) {
628             return rhs instanceof ExistingSection
629                     && backingAttributes.equals(((ExistingSection) rhs).backingAttributes);
630         }
631     }
632 
633     @Override
634     public Iterator<String> iterator() {
635         return getEntries().keySet().iterator();
636     }
637 
638     /**
639      * The main section of this manifest
640      */
641     private Section mainSection = new Section();
642 
643     /**
644      * Construct a manifest from Ant's default manifest file.
645      *
646      * @param minimalDefaultManifest
647      *            indicates whether a minimal manifest will be created, thus having only
648      *            {@code Manifest-Version: 1.0} in it.
649      *
650      * @return the default manifest.
651      *
652      * @throws ArchiverException
653      *             if there is a problem loading the default manifest
654      */
655     public static Manifest getDefaultManifest(boolean minimalDefaultManifest) throws ArchiverException {
656         final Manifest defaultManifest = new Manifest();
657         defaultManifest.getMainAttributes().putValue("Manifest-Version", "1.0");
658 
659         if (!minimalDefaultManifest) {
660             String createdBy = "Plexus Archiver";
661 
662             final String plexusArchiverVersion = JdkManifestFactory.getArchiverVersion();
663 
664             if (plexusArchiverVersion != null) {
665                 createdBy += " " + plexusArchiverVersion;
666             }
667 
668             defaultManifest.getMainAttributes().putValue("Created-By", createdBy);
669         }
670 
671         return defaultManifest;
672     }
673 
674     /**
675      * @see #getDefaultManifest(boolean)
676      */
677     public static Manifest getDefaultManifest() throws ArchiverException {
678         return getDefaultManifest(false);
679     }
680 
681     /**
682      * Construct an empty manifest
683      */
684     public Manifest() {
685         setManifestVersion();
686     }
687 
688     private void setManifestVersion() {
689         getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
690     }
691 
692     /**
693      * Read a manifest file from the given reader
694      *
695      * @param r is the reader from which the Manifest is read
696      *
697      * @throws ManifestException if the manifest is not valid according
698      * to the JAR spec
699      * @throws IOException if the manifest cannot be read from the reader.
700      * @deprecated This constructor does not properly map characters to bytes. Use
701      * {@link #Manifest(InputStream)}. Will be removed in 4.0.
702      */
703     @Deprecated
704     public Manifest(Reader r) throws ManifestException, IOException {
705         super(getInputStream(r));
706         setManifestVersion();
707     }
708 
709     public Manifest(InputStream is) throws IOException {
710         super(is);
711         setManifestVersion();
712     }
713 
714     /**
715      * Add a section to the manifest
716      *
717      * @param section the manifest section to be added
718      *
719      * @throws ManifestException if the secti0on is not valid.
720      */
721     public void addConfiguredSection(Section section) throws ManifestException {
722         String sectionName = section.getName();
723         if (sectionName == null) {
724             throw new ManifestException("Sections must have a name");
725         }
726         Attributes attributes = getOrCreateAttributes(sectionName);
727         for (String s : section.attributes.keySet()) {
728 
729             Attribute attribute = section.getAttribute(s);
730             attributes.putValue(attribute.getName(), attribute.getValue());
731         }
732     }
733 
734     private Attributes getOrCreateAttributes(String name) {
735         Attributes attributes = getAttributes(name);
736         if (attributes == null) {
737             attributes = new Attributes();
738             getEntries().put(name, attributes);
739         }
740         return attributes;
741     }
742 
743     /**
744      * Add an attribute to the manifest - it is added to the main section.
745      *
746      * @param attribute the attribute to be added.
747      *
748      * @throws ManifestException if the attribute is not valid.
749      */
750     public void addConfiguredAttribute(Attribute attribute) throws ManifestException {
751         remap(getMainAttributes(), attribute);
752     }
753 
754     /**
755      * Writes the manifest out to a writer.
756      *
757      * @param writer the Writer to which the manifest is written
758      *
759      * @throws IOException if the manifest cannot be written
760      */
761     public void write(Writer writer) throws IOException {
762         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
763         super.write(byteArrayOutputStream);
764         // We know that UTF-8 is the encoding of the JAR file specification
765         writer.write(byteArrayOutputStream.toString("UTF-8"));
766     }
767 
768     /**
769      * Convert the manifest to its string representation
770      *
771      * @return a multiline string with the Manifest as it
772      * appears in a Manifest file.
773      */
774     @Override
775     public String toString() {
776         StringWriter sw = new StringWriter();
777         try {
778             write(sw);
779         } catch (IOException e) {
780             return null;
781         }
782         return sw.toString();
783     }
784 
785     /**
786      * Get the warnings for this manifest.
787      *
788      * @return an enumeration of warning strings
789      */
790     Enumeration<String> getWarnings() {
791         Vector<String> warnings = new Vector<String>();
792 
793         Enumeration<String> warnEnum = mainSection.getWarnings();
794         while (warnEnum.hasMoreElements()) {
795             warnings.addElement(warnEnum.nextElement());
796         }
797 
798         return warnings.elements();
799     }
800 
801     /**
802      * Get the version of the manifest
803      *
804      * @return the manifest's version string
805      */
806     public String getManifestVersion() {
807         /*
808         The version of this manifest
809         */
810         return DEFAULT_MANIFEST_VERSION;
811     }
812 
813     /**
814      * Get the main section of the manifest
815      *
816      * @return the main section of the manifest
817      */
818     public ExistingSection getMainSection() {
819         return new ExistingSection(getMainAttributes(), null);
820     }
821 
822     /**
823      * Get a particular section from the manifest
824      *
825      * @param name the name of the section desired.
826      *
827      * @return the specified section or null if that section
828      * does not exist in the manifest
829      */
830     public ExistingSection getSection(String name) {
831         Attributes attributes = getAttributes(name);
832         if (attributes != null) {
833             return new ExistingSection(attributes, name);
834         }
835         return null;
836     }
837 
838     @Deprecated
839     private static InputStream getInputStream(Reader r) throws IOException {
840         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
841         int read;
842         while ((read = r.read()) != -1) {
843             byteArrayOutputStream.write(read);
844         }
845         return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
846     }
847 
848     public static String remap(Attributes backingAttributes, Attribute attribute) throws ManifestException {
849         if (attribute.getKey() == null || attribute.getValue() == null) {
850             throw new ManifestException("Attributes must have name and value");
851         }
852 
853         String attributeKey = attribute.getKey();
854         if (attributeKey.equalsIgnoreCase(ManifestConstants.ATTRIBUTE_CLASSPATH)) {
855             String classpathAttribute = backingAttributes.getValue(attributeKey);
856 
857             if (classpathAttribute == null) {
858                 classpathAttribute = attribute.getValue();
859             } else {
860                 classpathAttribute += " " + attribute.getValue();
861             }
862             backingAttributes.putValue(ManifestConstants.ATTRIBUTE_CLASSPATH, classpathAttribute);
863         } else {
864             backingAttributes.putValue(attribute.getName(), attribute.getValue());
865             if (attribute.getKey().equalsIgnoreCase(ATTRIBUTE_NAME)) {
866                 return attribute.getValue();
867             }
868         }
869         return null;
870     }
871 }