001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.shared.ldap.util;
021    
022    
023    import java.io.ByteArrayOutputStream;
024    import java.io.UnsupportedEncodingException;
025    import java.net.URI;
026    import java.text.ParseException;
027    import java.util.ArrayList;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Set;
031    
032    import org.apache.directory.shared.asn1.codec.binary.Hex;
033    import org.apache.directory.shared.i18n.I18n;
034    import org.apache.directory.shared.ldap.codec.util.HttpClientError;
035    import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
036    import org.apache.directory.shared.ldap.codec.util.URIException;
037    import org.apache.directory.shared.ldap.codec.util.UrlDecoderException;
038    import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
039    import org.apache.directory.shared.ldap.filter.FilterParser;
040    import org.apache.directory.shared.ldap.filter.SearchScope;
041    import org.apache.directory.shared.ldap.name.DN;
042    
043    
044    /**
045     * Decodes a LdapUrl, and checks that it complies with
046     * the RFC 2255. The grammar is the following :
047     * ldapurl    = scheme "://" [hostport] ["/"
048     *                   [dn ["?" [attributes] ["?" [scope]
049     *                   ["?" [filter] ["?" extensions]]]]]]
050     * scheme     = "ldap"
051     * attributes = attrdesc *("," attrdesc)
052     * scope      = "base" / "one" / "sub"
053     * dn         = DN
054     * hostport   = hostport from Section 5 of RFC 1738
055     * attrdesc   = AttributeDescription from Section 4.1.5 of RFC 2251
056     * filter     = filter from Section 4 of RFC 2254
057     * extensions = extension *("," extension)
058     * extension  = ["!"] extype ["=" exvalue]
059     * extype     = token / xtoken
060     * exvalue    = LDAPString
061     * token      = oid from section 4.1 of RFC 2252
062     * xtoken     = ("X-" / "x-") token
063     * 
064     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
065     * @version $Rev: 923455 $, $Date: 2010-03-15 22:59:28 +0100 (Mon, 15 Mar 2010) $, 
066     */
067    public class LdapURL
068    {
069    
070        // ~ Static fields/initializers
071        // -----------------------------------------------------------------
072    
073        /** The constant for "ldaps://" scheme. */
074        public static final String LDAPS_SCHEME = "ldaps://";
075    
076        /** The constant for "ldap://" scheme. */
077        public static final String LDAP_SCHEME = "ldap://";
078    
079        /** A null LdapURL */
080        public static final LdapURL EMPTY_URL = new LdapURL();
081    
082        // ~ Instance fields
083        // ----------------------------------------------------------------------------
084    
085        /** The scheme */
086        private String scheme;
087    
088        /** The host */
089        private String host;
090    
091        /** The port */
092        private int port;
093    
094        /** The DN */
095        private DN dn;
096    
097        /** The attributes */
098        private List<String> attributes;
099    
100        /** The scope */
101        private SearchScope scope;
102    
103        /** The filter as a string */
104        private String filter;
105    
106        /** The extensions. */
107        private List<Extension> extensionList;
108    
109        /** Stores the LdapURL as a String */
110        private String string;
111    
112        /** Stores the LdapURL as a byte array */
113        private byte[] bytes;
114    
115        /** modal parameter that forces explicit scope rendering in toString */
116        private boolean forceScopeRendering;
117    
118    
119        // ~ Constructors
120        // -------------------------------------------------------------------------------
121    
122        /**
123         * Construct an empty LdapURL
124         */
125        public LdapURL()
126        {
127            scheme = LDAP_SCHEME;
128            host = null;
129            port = -1;
130            dn = null;
131            attributes = new ArrayList<String>();
132            scope = SearchScope.OBJECT;
133            filter = null;
134            extensionList = new ArrayList<Extension>( 2 );
135        }
136    
137    
138        /**
139         * Parse a LdapURL
140         * @param chars The chars containing the URL
141         * @throws LdapURLEncodingException If the URL is invalid
142         */
143        public void parse( char[] chars ) throws LdapURLEncodingException
144        {
145            scheme = LDAP_SCHEME;
146            host = null;
147            port = -1;
148            dn = null;
149            attributes = new ArrayList<String>();
150            scope = SearchScope.OBJECT;
151            filter = null;
152            extensionList = new ArrayList<Extension>( 2 );
153    
154            if ( ( chars == null ) || ( chars.length == 0 ) )
155            {
156                host = "";
157                return;
158            }
159    
160            // ldapurl = scheme "://" [hostport] ["/"
161            // [dn ["?" [attributes] ["?" [scope]
162            // ["?" [filter] ["?" extensions]]]]]]
163            // scheme = "ldap"
164    
165            int pos = 0;
166    
167            // The scheme
168            if ( ( ( pos = StringTools.areEquals( chars, 0, LDAP_SCHEME ) ) == StringTools.NOT_EQUAL )
169                && ( ( pos = StringTools.areEquals( chars, 0, LDAPS_SCHEME ) ) == StringTools.NOT_EQUAL ) )
170            {
171                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04398 ) );
172            }
173            else
174            {
175                scheme = new String( chars, 0, pos );
176            }
177    
178            // The hostport
179            if ( ( pos = parseHostPort( chars, pos ) ) == -1 )
180            {
181                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04399 ) );
182            }
183    
184            if ( pos == chars.length )
185            {
186                return;
187            }
188    
189            // An optional '/'
190            if ( !StringTools.isCharASCII( chars, pos, '/' ) )
191            {
192                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04400, pos, chars[pos] ) );
193            }
194    
195            pos++;
196    
197            if ( pos == chars.length )
198            {
199                return;
200            }
201    
202            // An optional DN
203            if ( ( pos = parseDN( chars, pos ) ) == -1 )
204            {
205                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04401 ) );
206            }
207    
208            if ( pos == chars.length )
209            {
210                return;
211            }
212    
213            // Optionals attributes
214            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
215            {
216                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
217            }
218    
219            pos++;
220    
221            if ( ( pos = parseAttributes( chars, pos ) ) == -1 )
222            {
223                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04403 ) );
224            }
225    
226            if ( pos == chars.length )
227            {
228                return;
229            }
230    
231            // Optional scope
232            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
233            {
234                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
235            }
236    
237            pos++;
238    
239            if ( ( pos = parseScope( chars, pos ) ) == -1 )
240            {
241                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04404 ) );
242            }
243    
244            if ( pos == chars.length )
245            {
246                return;
247            }
248    
249            // Optional filter
250            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
251            {
252                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
253            }
254    
255            pos++;
256    
257            if ( pos == chars.length )
258            {
259                return;
260            }
261    
262            if ( ( pos = parseFilter( chars, pos ) ) == -1 )
263            {
264                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04405 ) );
265            }
266    
267            if ( pos == chars.length )
268            {
269                return;
270            }
271    
272            // Optional extensions
273            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
274            {
275                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
276            }
277    
278            pos++;
279    
280            if ( ( pos = parseExtensions( chars, pos ) ) == -1 )
281            {
282                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04406 ) );
283            }
284    
285            if ( pos == chars.length )
286            {
287                return;
288            }
289            else
290            {
291                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04407 ) );
292            }
293        }
294    
295    
296        /**
297         * Create a new LdapURL from a String after having parsed it.
298         * 
299         * @param string TheString that contains the LDAPURL
300         * @throws LdapURLEncodingException If the String does not comply with RFC 2255
301         */
302        public LdapURL( String string ) throws LdapURLEncodingException
303        {
304            if ( string == null )
305            {
306                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04408 ) );
307            }
308    
309            try
310            {
311                bytes = string.getBytes( "UTF-8" );
312                this.string = string;
313                parse( string.toCharArray() );
314            }
315            catch ( UnsupportedEncodingException uee )
316            {
317                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04409, string ) );
318            }
319        }
320    
321    
322        /**
323         * Create a new LdapURL after having parsed it.
324         * 
325         * @param bytes The byte buffer that contains the LDAPURL
326         * @throws LdapURLEncodingException If the byte array does not comply with RFC 2255
327         */
328        public LdapURL( byte[] bytes ) throws LdapURLEncodingException
329        {
330            if ( ( bytes == null ) || ( bytes.length == 0 ) )
331            {
332                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04410 ) );
333            }
334    
335            string = StringTools.utf8ToString( bytes );
336    
337            this.bytes = new byte[bytes.length];
338            System.arraycopy( bytes, 0, this.bytes, 0, bytes.length );
339    
340            parse( string.toCharArray() );
341        }
342    
343    
344        // ~ Methods
345        // ------------------------------------------------------------------------------------
346    
347        /**
348         * Parse this rule : <br>
349         * <p>
350         * &lt;host&gt; ::= &lt;hostname&gt; ':' &lt;hostnumber&gt;<br>
351         * &lt;hostname&gt; ::= *[ &lt;domainlabel&gt; "." ] &lt;toplabel&gt;<br>
352         * &lt;domainlabel&gt; ::= &lt;alphadigit&gt; | &lt;alphadigit&gt; *[
353         * &lt;alphadigit&gt; | "-" ] &lt;alphadigit&gt;<br>
354         * &lt;toplabel&gt; ::= &lt;alpha&gt; | &lt;alpha&gt; *[ &lt;alphadigit&gt; |
355         * "-" ] &lt;alphadigit&gt;<br>
356         * &lt;hostnumber&gt; ::= &lt;digits&gt; "." &lt;digits&gt; "."
357         * &lt;digits&gt; "." &lt;digits&gt;
358         * </p>
359         * 
360         * @param chars The buffer to parse
361         * @param pos The current position in the byte buffer
362         * @return The new position in the byte buffer, or -1 if the rule does not
363         *         apply to the byte buffer TODO check that the topLabel is valid
364         *         (it must start with an alpha)
365         */
366        private int parseHost( char[] chars, int pos )
367        {
368    
369            int start = pos;
370            boolean hadDot = false;
371            boolean hadMinus = false;
372            boolean isHostNumber = true;
373            boolean invalidIp = false;
374            int nbDots = 0;
375            int[] ipElem = new int[4];
376    
377            // The host will be followed by a '/' or a ':', or by nothing if it's
378            // the end.
379            // We will search the end of the host part, and we will check some
380            // elements.
381            if ( StringTools.isCharASCII( chars, pos, '-' ) )
382            {
383    
384                // We can't have a '-' on first position
385                return -1;
386            }
387    
388            while ( ( pos < chars.length ) && ( chars[pos] != ':' ) && ( chars[pos] != '/' ) )
389            {
390    
391                if ( StringTools.isCharASCII( chars, pos, '.' ) )
392                {
393    
394                    if ( ( hadMinus ) || ( hadDot ) )
395                    {
396    
397                        // We already had a '.' just before : this is not allowed.
398                        // Or we had a '-' before a '.' : ths is not allowed either.
399                        return -1;
400                    }
401    
402                    // Let's check the string we had before the dot.
403                    if ( isHostNumber )
404                    {
405    
406                        if ( nbDots < 4 )
407                        {
408    
409                            // We had only digits. It may be an IP adress? Check it
410                            if ( ipElem[nbDots] > 65535 )
411                            {
412                                invalidIp = true;
413                            }
414                        }
415                    }
416    
417                    hadDot = true;
418                    nbDots++;
419                    pos++;
420                    continue;
421                }
422                else
423                {
424    
425                    if ( hadDot && StringTools.isCharASCII( chars, pos, '-' ) )
426                    {
427    
428                        // We can't have a '-' just after a '.'
429                        return -1;
430                    }
431    
432                    hadDot = false;
433                }
434    
435                if ( StringTools.isDigit( chars, pos ) )
436                {
437    
438                    if ( isHostNumber && ( nbDots < 4 ) )
439                    {
440                        ipElem[nbDots] = ( ipElem[nbDots] * 10 ) + ( chars[pos] - '0' );
441    
442                        if ( ipElem[nbDots] > 65535 )
443                        {
444                            invalidIp = true;
445                        }
446                    }
447    
448                    hadMinus = false;
449                }
450                else if ( StringTools.isAlphaDigitMinus( chars, pos ) )
451                {
452                    isHostNumber = false;
453    
454                    if ( StringTools.isCharASCII( chars, pos, '-' ) )
455                    {
456                        hadMinus = true;
457                    }
458                    else
459                    {
460                        hadMinus = false;
461                    }
462                }
463                else
464                {
465                    return -1;
466                }
467    
468                pos++;
469            }
470    
471            if ( start == pos )
472            {
473    
474                // An empty host is valid
475                return pos;
476            }
477    
478            // Checks the hostNumber
479            if ( isHostNumber )
480            {
481    
482                // As this is a host number, we must have 3 dots.
483                if ( nbDots != 3 )
484                {
485                    return -1;
486                }
487    
488                if ( invalidIp )
489                {
490                    return -1;
491                }
492            }
493    
494            // Check if we have a '.' or a '-' in last position
495            if ( hadDot || hadMinus )
496            {
497                return -1;
498            }
499    
500            host = new String( chars, start, pos - start );
501    
502            return pos;
503        }
504    
505    
506        /**
507         * Parse this rule : <br>
508         * <p>
509         * &lt;port&gt; ::= &lt;digits&gt;<br>
510         * &lt;digits&gt; ::= &lt;digit&gt; &lt;digits-or-null&gt;<br>
511         * &lt;digits-or-null&gt; ::= &lt;digit&gt; &lt;digits-or-null&gt; | e<br>
512         * &lt;digit&gt; ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
513         * </p>
514         * The port must be between 0 and 65535.
515         * 
516         * @param chars The buffer to parse
517         * @param pos The current position in the byte buffer
518         * @return The new position in the byte buffer, or -1 if the rule does not
519         *         apply to the byte buffer
520         */
521        private int parsePort( char[] chars, int pos )
522        {
523    
524            if ( !StringTools.isDigit( chars, pos ) )
525            {
526                return -1;
527            }
528    
529            port = chars[pos] - '0';
530    
531            pos++;
532    
533            while ( StringTools.isDigit( chars, pos ) )
534            {
535                port = ( port * 10 ) + ( chars[pos] - '0' );
536    
537                if ( port > 65535 )
538                {
539                    return -1;
540                }
541    
542                pos++;
543            }
544    
545            return pos;
546        }
547    
548    
549        /**
550         * Parse this rule : <br>
551         * <p>
552         * &lt;hostport&gt; ::= &lt;host&gt; ':' &lt;port&gt;
553         * </p>
554         * 
555         * @param chars The char array to parse
556         * @param pos The current position in the byte buffer
557         * @return The new position in the byte buffer, or -1 if the rule does not
558         *         apply to the byte buffer
559         */
560        private int parseHostPort( char[] chars, int pos )
561        {
562            int hostPos = pos;
563    
564            if ( ( pos = parseHost( chars, pos ) ) == -1 )
565            {
566                return -1;
567            }
568    
569            // We may have a port.
570            if ( StringTools.isCharASCII( chars, pos, ':' ) )
571            {
572                if ( pos == hostPos )
573                {
574                    // We should not have a port if we have no host
575                    return -1;
576                }
577    
578                pos++;
579            }
580            else
581            {
582                return pos;
583            }
584    
585            // As we have a ':', we must have a valid port (between 0 and 65535).
586            if ( ( pos = parsePort( chars, pos ) ) == -1 )
587            {
588                return -1;
589            }
590    
591            return pos;
592        }
593    
594    
595        /**
596         * From commons-httpclients. Converts the byte array of HTTP content
597         * characters to a string. If the specified charset is not supported,
598         * default system encoding is used.
599         * 
600         * @param data the byte array to be encoded
601         * @param offset the index of the first byte to encode
602         * @param length the number of bytes to encode
603         * @param charset the desired character encoding
604         * @return The result of the conversion.
605         * @since 3.0
606         */
607        public static String getString( final byte[] data, int offset, int length, String charset )
608        {
609            if ( data == null )
610            {
611                throw new IllegalArgumentException( I18n.err( I18n.ERR_04411 ) );
612            }
613    
614            if ( charset == null || charset.length() == 0 )
615            {
616                throw new IllegalArgumentException( I18n.err( I18n.ERR_04412 ) );
617            }
618    
619            try
620            {
621                return new String( data, offset, length, charset );
622            }
623            catch ( UnsupportedEncodingException e )
624            {
625                return new String( data, offset, length );
626            }
627        }
628    
629    
630        /**
631         * From commons-httpclients. Converts the byte array of HTTP content
632         * characters to a string. If the specified charset is not supported,
633         * default system encoding is used.
634         * 
635         * @param data the byte array to be encoded
636         * @param charset the desired character encoding
637         * @return The result of the conversion.
638         * @since 3.0
639         */
640        public static String getString( final byte[] data, String charset )
641        {
642            return getString( data, 0, data.length, charset );
643        }
644    
645    
646        /**
647         * Converts the specified string to byte array of ASCII characters.
648         * 
649         * @param data the string to be encoded
650         * @return The string as a byte array.
651         * @since 3.0
652         */
653        public static byte[] getAsciiBytes( final String data )
654        {
655    
656            if ( data == null )
657            {
658                throw new IllegalArgumentException( I18n.err( I18n.ERR_04411 ) );
659            }
660    
661            try
662            {
663                return data.getBytes( "US-ASCII" );
664            }
665            catch ( UnsupportedEncodingException e )
666            {
667                throw new HttpClientError( I18n.err( I18n.ERR_04413 ) );
668            }
669        }
670    
671    
672        /**
673         * From commons-codec. Decodes an array of URL safe 7-bit characters into an
674         * array of original bytes. Escaped characters are converted back to their
675         * original representation.
676         * 
677         * @param bytes array of URL safe characters
678         * @return array of original bytes
679         * @throws UrlDecoderException Thrown if URL decoding is unsuccessful
680         */
681        private static final byte[] decodeUrl( byte[] bytes ) throws UrlDecoderException
682        {
683            if ( bytes == null )
684            {
685                return StringTools.EMPTY_BYTES;
686            }
687    
688            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
689    
690            for ( int i = 0; i < bytes.length; i++ )
691            {
692                int b = bytes[i];
693    
694                if ( b == '%' )
695                {
696                    try
697                    {
698                        int u = Character.digit( ( char ) bytes[++i], 16 );
699                        int l = Character.digit( ( char ) bytes[++i], 16 );
700    
701                        if ( u == -1 || l == -1 )
702                        {
703                            throw new UrlDecoderException( I18n.err( I18n.ERR_04414 ) );
704                        }
705    
706                        buffer.write( ( char ) ( ( u << 4 ) + l ) );
707                    }
708                    catch ( ArrayIndexOutOfBoundsException e )
709                    {
710                        throw new UrlDecoderException( I18n.err( I18n.ERR_04414 ) );
711                    }
712                }
713                else
714                {
715                    buffer.write( b );
716                }
717            }
718    
719            return buffer.toByteArray();
720        }
721    
722    
723        /**
724         * From commons-httpclients. Unescape and decode a given string regarded as
725         * an escaped string with the default protocol charset.
726         * 
727         * @param escaped a string
728         * @return the unescaped string
729         * @throws URIException if the string cannot be decoded (invalid)
730         * @see URI#getDefaultProtocolCharset
731         */
732        private static String decode( String escaped ) throws URIException
733        {
734            try
735            {
736                byte[] rawdata = decodeUrl( getAsciiBytes( escaped ) );
737                return getString( rawdata, "UTF-8" );
738            }
739            catch ( UrlDecoderException e )
740            {
741                throw new URIException( e.getMessage() );
742            }
743        }
744    
745    
746        /**
747         * Parse a string and check that it complies with RFC 2253. Here, we will
748         * just call the DN parser to do the job.
749         * 
750         * @param chars The char array to be checked
751         * @param pos the starting position
752         * @return -1 if the char array does not contains a DN
753         */
754        private int parseDN( char[] chars, int pos )
755        {
756    
757            int end = pos;
758    
759            for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
760            {
761                end++;
762            }
763    
764            try
765            {
766                dn = new DN( decode( new String( chars, pos, end - pos ) ) );
767            }
768            catch ( URIException ue )
769            {
770                return -1;
771            }
772            catch ( LdapInvalidDnException de )
773            {
774                return -1;
775            }
776    
777            return end;
778        }
779    
780    
781        /**
782         * Parse the attributes part
783         * 
784         * @param chars The char array to be checked
785         * @param pos the starting position
786         * @return -1 if the char array does not contains attributes
787         */
788        private int parseAttributes( char[] chars, int pos )
789        {
790    
791            int start = pos;
792            int end = pos;
793            Set<String> hAttributes = new HashSet<String>();
794            boolean hadComma = false;
795    
796            try
797            {
798    
799                for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
800                {
801    
802                    if ( StringTools.isCharASCII( chars, i, ',' ) )
803                    {
804                        hadComma = true;
805    
806                        if ( ( end - start ) == 0 )
807                        {
808    
809                            // An attributes must not be null
810                            return -1;
811                        }
812                        else
813                        {
814                            String attribute = null;
815    
816                            // get the attribute. It must not be blank
817                            attribute = new String( chars, start, end - start ).trim();
818    
819                            if ( attribute.length() == 0 )
820                            {
821                                return -1;
822                            }
823    
824                            String decodedAttr = decode( attribute );
825    
826                            if ( !hAttributes.contains( decodedAttr ) )
827                            {
828                                attributes.add( decodedAttr );
829                                hAttributes.add( decodedAttr );
830                            }
831                        }
832    
833                        start = i + 1;
834                    }
835                    else
836                    {
837                        hadComma = false;
838                    }
839    
840                    end++;
841                }
842    
843                if ( hadComma )
844                {
845    
846                    // We are not allowed to have a comma at the end of the
847                    // attributes
848                    return -1;
849                }
850                else
851                {
852    
853                    if ( end == start )
854                    {
855    
856                        // We don't have any attributes. This is valid.
857                        return end;
858                    }
859    
860                    // Store the last attribute
861                    // get the attribute. It must not be blank
862                    String attribute = null;
863    
864                    attribute = new String( chars, start, end - start ).trim();
865    
866                    if ( attribute.length() == 0 )
867                    {
868                        return -1;
869                    }
870    
871                    String decodedAttr = decode( attribute );
872    
873                    if ( !hAttributes.contains( decodedAttr ) )
874                    {
875                        attributes.add( decodedAttr );
876                        hAttributes.add( decodedAttr );
877                    }
878                }
879    
880                return end;
881            }
882            catch ( URIException ue )
883            {
884                return -1;
885            }
886        }
887    
888    
889        /**
890         * Parse the filter part. We will use the FilterParserImpl class
891         * 
892         * @param chars The char array to be checked
893         * @param pos the starting position
894         * @return -1 if the char array does not contains a filter
895         */
896        private int parseFilter( char[] chars, int pos )
897        {
898    
899            int end = pos;
900    
901            for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
902            {
903                end++;
904            }
905    
906            if ( end == pos )
907            {
908                // We have no filter
909                return end;
910            }
911    
912            try
913            {
914                filter = decode( new String( chars, pos, end - pos ) );
915                FilterParser.parse( filter );
916            }
917            catch ( URIException ue )
918            {
919                return -1;
920            }
921            catch ( ParseException pe )
922            {
923                return -1;
924            }
925    
926            return end;
927        }
928    
929    
930        /**
931         * Parse the scope part.
932         * 
933         * @param chars The char array to be checked
934         * @param pos the starting position
935         * @return -1 if the char array does not contains a scope
936         */
937        private int parseScope( char[] chars, int pos )
938        {
939    
940            if ( StringTools.isCharASCII( chars, pos, 'b' ) || StringTools.isCharASCII( chars, pos, 'B' ) )
941            {
942                pos++;
943    
944                if ( StringTools.isCharASCII( chars, pos, 'a' ) || StringTools.isCharASCII( chars, pos, 'A' ) )
945                {
946                    pos++;
947    
948                    if ( StringTools.isCharASCII( chars, pos, 's' ) || StringTools.isCharASCII( chars, pos, 'S' ) )
949                    {
950                        pos++;
951    
952                        if ( StringTools.isCharASCII( chars, pos, 'e' ) || StringTools.isCharASCII( chars, pos, 'E' ) )
953                        {
954                            pos++;
955                            scope = SearchScope.OBJECT;
956                            return pos;
957                        }
958                    }
959                }
960            }
961            else if ( StringTools.isCharASCII( chars, pos, 'o' ) || StringTools.isCharASCII( chars, pos, 'O' ) )
962            {
963                pos++;
964    
965                if ( StringTools.isCharASCII( chars, pos, 'n' ) || StringTools.isCharASCII( chars, pos, 'N' ) )
966                {
967                    pos++;
968    
969                    if ( StringTools.isCharASCII( chars, pos, 'e' ) || StringTools.isCharASCII( chars, pos, 'E' ) )
970                    {
971                        pos++;
972    
973                        scope = SearchScope.ONELEVEL;
974                        return pos;
975                    }
976                }
977            }
978            else if ( StringTools.isCharASCII( chars, pos, 's' ) || StringTools.isCharASCII( chars, pos, 'S' ) )
979            {
980                pos++;
981    
982                if ( StringTools.isCharASCII( chars, pos, 'u' ) || StringTools.isCharASCII( chars, pos, 'U' ) )
983                {
984                    pos++;
985    
986                    if ( StringTools.isCharASCII( chars, pos, 'b' ) || StringTools.isCharASCII( chars, pos, 'B' ) )
987                    {
988                        pos++;
989    
990                        scope = SearchScope.SUBTREE;
991                        return pos;
992                    }
993                }
994            }
995            else if ( StringTools.isCharASCII( chars, pos, '?' ) )
996            {
997                // An empty scope. This is valid
998                return pos;
999            }
1000            else if ( pos == chars.length )
1001            {
1002                // An empty scope at the end of the URL. This is valid
1003                return pos;
1004            }
1005    
1006            // The scope is not one of "one", "sub" or "base". It's an error
1007            return -1;
1008        }
1009    
1010    
1011        /**
1012         * Parse extensions and critical extensions. 
1013         * 
1014         * The grammar is : 
1015         * extensions ::= extension [ ',' extension ]* 
1016         * extension ::= [ '!' ] ( token | ( 'x-' | 'X-' ) token ) ) [ '=' exvalue ]
1017         * 
1018         * @param chars The char array to be checked
1019         * @param pos the starting position
1020         * @return -1 if the char array does not contains valid extensions or
1021         *         critical extensions
1022         */
1023        private int parseExtensions( char[] chars, int pos )
1024        {
1025            int start = pos;
1026            boolean isCritical = false;
1027            boolean isNewExtension = true;
1028            boolean hasValue = false;
1029            String extension = null;
1030            String value = null;
1031    
1032            if ( pos == chars.length )
1033            {
1034                return pos;
1035            }
1036    
1037            try
1038            {
1039                for ( int i = pos; ( i < chars.length ); i++ )
1040                {
1041                    if ( StringTools.isCharASCII( chars, i, ',' ) )
1042                    {
1043                        if ( isNewExtension )
1044                        {
1045                            // a ',' is not allowed when we have already had one
1046                            // or if we just started to parse the extensions.
1047                            return -1;
1048                        }
1049                        else
1050                        {
1051                            if ( extension == null )
1052                            {
1053                                extension = decode( new String( chars, start, i - start ) ).trim();
1054                            }
1055                            else
1056                            {
1057                                value = decode( new String( chars, start, i - start ) ).trim();
1058                            }
1059    
1060                            Extension ext = new Extension( isCritical, extension, value );
1061                            extensionList.add( ext );
1062    
1063                            isNewExtension = true;
1064                            hasValue = false;
1065                            isCritical = false;
1066                            start = i + 1;
1067                            extension = null;
1068                            value = null;
1069                        }
1070                    }
1071                    else if ( StringTools.isCharASCII( chars, i, '=' ) )
1072                    {
1073                        if ( hasValue )
1074                        {
1075                            // We may have two '=' for the same extension
1076                            continue;
1077                        }
1078    
1079                        // An optionnal value
1080                        extension = decode( new String( chars, start, i - start ) ).trim();
1081    
1082                        if ( extension.length() == 0 )
1083                        {
1084                            // We must have an extension
1085                            return -1;
1086                        }
1087    
1088                        hasValue = true;
1089                        start = i + 1;
1090                    }
1091                    else if ( StringTools.isCharASCII( chars, i, '!' ) )
1092                    {
1093                        if ( hasValue )
1094                        {
1095                            // We may have two '!' in the value
1096                            continue;
1097                        }
1098    
1099                        if ( !isNewExtension )
1100                        {
1101                            // '!' must appears first
1102                            return -1;
1103                        }
1104    
1105                        isCritical = true;
1106                        start++;
1107                    }
1108                    else
1109                    {
1110                        isNewExtension = false;
1111                    }
1112                }
1113    
1114                if ( extension == null )
1115                {
1116                    extension = decode( new String( chars, start, chars.length - start ) ).trim();
1117                }
1118                else
1119                {
1120                    value = decode( new String( chars, start, chars.length - start ) ).trim();
1121                }
1122    
1123                Extension ext = new Extension( isCritical, extension, value );
1124                extensionList.add( ext );
1125    
1126                return chars.length;
1127            }
1128            catch ( URIException ue )
1129            {
1130                return -1;
1131            }
1132        }
1133    
1134    
1135        /**
1136         * Encode a String to avoid special characters.
1137         *
1138         * 
1139         * RFC 4516, section 2.1. (Percent-Encoding)
1140         *
1141         * A generated LDAP URL MUST consist only of the restricted set of
1142         * characters included in one of the following three productions defined
1143         * in [RFC3986]:
1144         *
1145         *   <reserved>
1146         *   <unreserved>
1147         *   <pct-encoded>
1148         *
1149         * Implementations SHOULD accept other valid UTF-8 strings [RFC3629] as
1150         * input.  An octet MUST be encoded using the percent-encoding mechanism
1151         * described in section 2.1 of [RFC3986] in any of these situations:
1152         * 
1153         *  The octet is not in the reserved set defined in section 2.2 of
1154         *  [RFC3986] or in the unreserved set defined in section 2.3 of
1155         *  [RFC3986].
1156         *
1157         *  It is the single Reserved character '?' and occurs inside a <dn>,
1158         *  <filter>, or other element of an LDAP URL.
1159         *
1160         *  It is a comma character ',' that occurs inside an <exvalue>.
1161         *
1162         *
1163         * RFC 3986, section 2.2 (Reserved Characters)
1164         * 
1165         * reserved    = gen-delims / sub-delims
1166         * gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
1167         * sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
1168         *              / "*" / "+" / "," / ";" / "="
1169         *             
1170         *             
1171         * RFC 3986, section 2.3 (Unreserved Characters)
1172         * 
1173         * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
1174         *
1175         * 
1176         * @param url The String to encode
1177         * @param doubleEncode Set if we need to encode the comma
1178         * @return An encoded string
1179         */
1180        public static String urlEncode( String url, boolean doubleEncode )
1181        {
1182            StringBuffer sb = new StringBuffer();
1183    
1184            for ( int i = 0; i < url.length(); i++ )
1185            {
1186                char c = url.charAt( i );
1187    
1188                switch ( c )
1189    
1190                {
1191                    // reserved and unreserved characters:
1192                    // just append to the buffer
1193    
1194                    // reserved gen-delims, excluding '?'
1195                    // gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
1196                    case ':':
1197                    case '/':
1198                    case '#':
1199                    case '[':
1200                    case ']':
1201                    case '@':
1202    
1203                        // reserved sub-delims, excluding ','
1204                        // sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
1205                        //               / "*" / "+" / "," / ";" / "="
1206                    case '!':
1207                    case '$':
1208                    case '&':
1209                    case '\'':
1210                    case '(':
1211                    case ')':
1212                    case '*':
1213                    case '+':
1214                    case ';':
1215                    case '=':
1216    
1217                        // unreserved
1218                        // unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
1219                    case 'a':
1220                    case 'b':
1221                    case 'c':
1222                    case 'd':
1223                    case 'e':
1224                    case 'f':
1225                    case 'g':
1226                    case 'h':
1227                    case 'i':
1228                    case 'j':
1229                    case 'k':
1230                    case 'l':
1231                    case 'm':
1232                    case 'n':
1233                    case 'o':
1234                    case 'p':
1235                    case 'q':
1236                    case 'r':
1237                    case 's':
1238                    case 't':
1239                    case 'u':
1240                    case 'v':
1241                    case 'w':
1242                    case 'x':
1243                    case 'y':
1244                    case 'z':
1245    
1246                    case 'A':
1247                    case 'B':
1248                    case 'C':
1249                    case 'D':
1250                    case 'E':
1251                    case 'F':
1252                    case 'G':
1253                    case 'H':
1254                    case 'I':
1255                    case 'J':
1256                    case 'K':
1257                    case 'L':
1258                    case 'M':
1259                    case 'N':
1260                    case 'O':
1261                    case 'P':
1262                    case 'Q':
1263                    case 'R':
1264                    case 'S':
1265                    case 'T':
1266                    case 'U':
1267                    case 'V':
1268                    case 'W':
1269                    case 'X':
1270                    case 'Y':
1271                    case 'Z':
1272    
1273                    case '0':
1274                    case '1':
1275                    case '2':
1276                    case '3':
1277                    case '4':
1278                    case '5':
1279                    case '6':
1280                    case '7':
1281                    case '8':
1282                    case '9':
1283    
1284                    case '-':
1285                    case '.':
1286                    case '_':
1287                    case '~':
1288    
1289                        sb.append( c );
1290                        break;
1291    
1292                    case ',':
1293    
1294                        // special case for comma
1295                        if ( doubleEncode )
1296                        {
1297                            sb.append( "%2c" );
1298                        }
1299                        else
1300                        {
1301                            sb.append( c );
1302                        }
1303                        break;
1304    
1305                    default:
1306    
1307                        // percent encoding
1308                        byte[] bytes = StringTools.charToBytes( c );
1309                        char[] hex = Hex.encodeHex( bytes );
1310                        for ( int j = 0; j < hex.length; j++ )
1311                        {
1312                            if ( j % 2 == 0 )
1313                            {
1314                                sb.append( '%' );
1315                            }
1316                            sb.append( hex[j] );
1317    
1318                        }
1319    
1320                        break;
1321                }
1322            }
1323    
1324            return sb.toString();
1325        }
1326    
1327    
1328        /**
1329         * Get a string representation of a LdapURL.
1330         * 
1331         * @return A LdapURL string
1332         * @see LdapURL#forceScopeRendering
1333         */
1334        public String toString()
1335        {
1336            StringBuffer sb = new StringBuffer();
1337    
1338            sb.append( scheme );
1339    
1340            sb.append( ( host == null ) ? "" : host );
1341    
1342            if ( port != -1 )
1343            {
1344                sb.append( ':' ).append( port );
1345            }
1346    
1347            if ( dn != null )
1348            {
1349                sb.append( '/' ).append( urlEncode( dn.getName(), false ) );
1350    
1351                if ( attributes.size() != 0 || forceScopeRendering
1352                    || ( ( scope != SearchScope.OBJECT ) || ( filter != null ) || ( extensionList.size() != 0 ) ) )
1353                {
1354                    sb.append( '?' );
1355    
1356                    boolean isFirst = true;
1357    
1358                    for ( String attribute : attributes )
1359                    {
1360                        if ( isFirst )
1361                        {
1362                            isFirst = false;
1363                        }
1364                        else
1365                        {
1366                            sb.append( ',' );
1367                        }
1368    
1369                        sb.append( urlEncode( attribute, false ) );
1370                    }
1371                }
1372    
1373                if ( forceScopeRendering )
1374                {
1375                    sb.append( '?' );
1376    
1377                    sb.append( scope.getLdapUrlValue() );
1378                }
1379    
1380                else
1381                {
1382                    if ( ( scope != SearchScope.OBJECT ) || ( filter != null ) || ( extensionList.size() != 0 ) )
1383                    {
1384                        sb.append( '?' );
1385    
1386                        switch ( scope )
1387                        {
1388                            case ONELEVEL:
1389                            case SUBTREE:
1390                                sb.append( scope.getLdapUrlValue() );
1391                                break;
1392    
1393                            default:
1394                                break;
1395                        }
1396    
1397                        if ( ( filter != null ) || ( ( extensionList.size() != 0 ) ) )
1398                        {
1399                            sb.append( "?" );
1400    
1401                            if ( filter != null )
1402                            {
1403                                sb.append( urlEncode( filter, false ) );
1404                            }
1405    
1406                            if ( ( extensionList.size() != 0 ) )
1407                            {
1408                                sb.append( '?' );
1409    
1410                                boolean isFirst = true;
1411    
1412                                if ( extensionList.size() != 0 )
1413                                {
1414                                    for ( Extension extension : extensionList )
1415                                    {
1416                                        if ( !isFirst )
1417                                        {
1418                                            sb.append( ',' );
1419                                        }
1420                                        else
1421                                        {
1422                                            isFirst = false;
1423                                        }
1424    
1425                                        if ( extension.isCritical )
1426                                        {
1427                                            sb.append( '!' );
1428                                        }
1429                                        sb.append( urlEncode( extension.type, false ) );
1430    
1431                                        if ( extension.value != null )
1432                                        {
1433                                            sb.append( '=' );
1434                                            sb.append( urlEncode( extension.value, true ) );
1435                                        }
1436                                    }
1437                                }
1438                            }
1439                        }
1440                    }
1441                }
1442            }
1443            else
1444            {
1445                sb.append( '/' );
1446            }
1447    
1448            return sb.toString();
1449        }
1450    
1451    
1452        /**
1453         * @return Returns the attributes.
1454         */
1455        public List<String> getAttributes()
1456        {
1457            return attributes;
1458        }
1459    
1460    
1461        /**
1462         * @return Returns the dn.
1463         */
1464        public DN getDn()
1465        {
1466            return dn;
1467        }
1468    
1469    
1470        /**
1471         * @return Returns the extensions.
1472         */
1473        public List<Extension> getExtensions()
1474        {
1475            return extensionList;
1476        }
1477    
1478    
1479        /**
1480         * Gets the extension.
1481         * 
1482         * @param type the extension type, case-insensitive
1483         * 
1484         * @return Returns the extension, null if this URL does not contain 
1485         *         such an extension.
1486         */
1487        public Extension getExtension( String type )
1488        {
1489            for ( Extension extension : getExtensions() )
1490            {
1491                if ( extension.getType().equalsIgnoreCase( type ) )
1492                {
1493                    return extension;
1494                }
1495            }
1496            return null;
1497        }
1498    
1499    
1500        /**
1501         * Gets the extension value.
1502         * 
1503         * @param type the extension type, case-insensitive
1504         * 
1505         * @return Returns the extension value, null if this URL does not  
1506         *         contain such an extension or if the extension value is null.
1507         */
1508        public String getExtensionValue( String type )
1509        {
1510            for ( Extension extension : getExtensions() )
1511            {
1512                if ( extension.getType().equalsIgnoreCase( type ) )
1513                {
1514                    return extension.getValue();
1515                }
1516            }
1517            return null;
1518        }
1519    
1520    
1521        /**
1522         * @return Returns the filter.
1523         */
1524        public String getFilter()
1525        {
1526            return filter;
1527        }
1528    
1529    
1530        /**
1531         * @return Returns the host.
1532         */
1533        public String getHost()
1534        {
1535            return host;
1536        }
1537    
1538    
1539        /**
1540         * @return Returns the port.
1541         */
1542        public int getPort()
1543        {
1544            return port;
1545        }
1546    
1547    
1548        /**
1549         * Returns the scope, one of {@link SearchScope.OBJECT}, 
1550         * {@link SearchScope.ONELEVEL} or {@link SearchScope.SUBTREE}.
1551         * 
1552         * @return Returns the scope.
1553         */
1554        public SearchScope getScope()
1555        {
1556            return scope;
1557        }
1558    
1559    
1560        /**
1561         * @return Returns the scheme.
1562         */
1563        public String getScheme()
1564        {
1565            return scheme;
1566        }
1567    
1568    
1569        /**
1570         * @return the number of bytes for this LdapURL
1571         */
1572        public int getNbBytes()
1573        {
1574            return ( bytes != null ? bytes.length : 0 );
1575        }
1576    
1577    
1578        /**
1579         * @return a reference on the interned bytes representing this LdapURL
1580         */
1581        public byte[] getBytesReference()
1582        {
1583            return bytes;
1584        }
1585    
1586    
1587        /**
1588         * @return a copy of the bytes representing this LdapURL
1589         */
1590        public byte[] getBytesCopy()
1591        {
1592            if ( bytes != null )
1593            {
1594                byte[] copy = new byte[bytes.length];
1595                System.arraycopy( bytes, 0, copy, 0, bytes.length );
1596                return copy;
1597            }
1598            else
1599            {
1600                return null;
1601            }
1602        }
1603    
1604    
1605        /**
1606         * @return the LdapURL as a String
1607         */
1608        public String getString()
1609        {
1610            return string;
1611        }
1612    
1613    
1614        /**
1615         * Compute the instance's hash code
1616         * @return the instance's hash code 
1617         */
1618        public int hashCode()
1619        {
1620            return this.toString().hashCode();
1621        }
1622    
1623    
1624        public boolean equals( Object obj )
1625        {
1626            if ( this == obj )
1627            {
1628                return true;
1629            }
1630            if ( obj == null )
1631            {
1632                return false;
1633            }
1634            if ( getClass() != obj.getClass() )
1635            {
1636                return false;
1637            }
1638    
1639            final LdapURL other = ( LdapURL ) obj;
1640            return this.toString().equals( other.toString() );
1641        }
1642    
1643    
1644        /**
1645         * Sets the scheme. Must be "ldap://" or "ldaps://", otherwise "ldap://" is assumed as default.
1646         * 
1647         * @param scheme the new scheme
1648         */
1649        public void setScheme( String scheme )
1650        {
1651            if ( scheme != null && LDAP_SCHEME.equals( scheme ) || LDAPS_SCHEME.equals( scheme ) )
1652            {
1653                this.scheme = scheme;
1654            }
1655            else
1656            {
1657                this.scheme = LDAP_SCHEME;
1658            }
1659    
1660        }
1661    
1662    
1663        /**
1664         * Sets the host.
1665         * 
1666         * @param host the new host
1667         */
1668        public void setHost( String host )
1669        {
1670            this.host = host;
1671        }
1672    
1673    
1674        /**
1675         * Sets the port. Must be between 1 and 65535, otherwise -1 is assumed as default.
1676         * 
1677         * @param port the new port
1678         */
1679        public void setPort( int port )
1680        {
1681            if ( port < 1 || port > 65535 )
1682            {
1683                this.port = -1;
1684            }
1685            else
1686            {
1687                this.port = port;
1688            }
1689        }
1690    
1691    
1692        /**
1693         * Sets the dn.
1694         * 
1695         * @param dn the new dn
1696         */
1697        public void setDn( DN dn )
1698        {
1699            this.dn = dn;
1700        }
1701    
1702    
1703        /**
1704         * Sets the attributes, null removes all existing attributes.
1705         * 
1706         * @param attributes the new attributes
1707         */
1708        public void setAttributes( List<String> attributes )
1709        {
1710            if ( attributes == null )
1711            {
1712                this.attributes.clear();
1713            }
1714            else
1715            {
1716                this.attributes = attributes;
1717            }
1718        }
1719    
1720    
1721        /**
1722         * Sets the scope. Must be one of {@link SearchScope.OBJECT}, 
1723         * {@link SearchScope.ONELEVEL} or {@link SearchScope.SUBTREE},
1724         * otherwise {@link SearchScope.OBJECT} is assumed as default.
1725         * 
1726         * @param scope the new scope
1727         */
1728        public void setScope( int scope )
1729        {
1730            try 
1731            {
1732                this.scope = SearchScope.getSearchScope( scope );
1733            }
1734            catch ( IllegalArgumentException iae )
1735            {
1736                this.scope = SearchScope.OBJECT;
1737            }
1738        }
1739    
1740    
1741        /**
1742         * Sets the scope. Must be one of {@link SearchScope.OBJECT}, 
1743         * {@link SearchScope.ONELEVEL} or {@link SearchScope.SUBTREE},
1744         * otherwise {@link SearchScope.OBJECT} is assumed as default.
1745         * 
1746         * @param scope the new scope
1747         */
1748        public void setScope( SearchScope scope )
1749        {
1750            if ( scope == null )
1751            {
1752                this.scope = SearchScope.OBJECT;
1753            }
1754            else
1755            {
1756                this.scope = scope;
1757            }
1758        }
1759    
1760    
1761        /**
1762         * Sets the filter.
1763         * 
1764         * @param filter the new filter
1765         */
1766        public void setFilter( String filter )
1767        {
1768            this.filter = filter;
1769        }
1770    
1771    
1772        /**
1773         * If set to true forces the toString method to render the scope 
1774         * regardless of optional nature.  Use this when you want explicit
1775         * search URL scope rendering.
1776         * 
1777         * @param forceScopeRendering the forceScopeRendering to set
1778         */
1779        public void setForceScopeRendering( boolean forceScopeRendering )
1780        {
1781            this.forceScopeRendering = forceScopeRendering;
1782        }
1783    
1784    
1785        /**
1786         * If set to true forces the toString method to render the scope 
1787         * regardless of optional nature.  Use this when you want explicit
1788         * search URL scope rendering.
1789         * 
1790         * @return the forceScopeRendering
1791         */
1792        public boolean isForceScopeRendering()
1793        {
1794            return forceScopeRendering;
1795        }
1796    
1797        /**
1798         * An inner bean to hold extension information.
1799         *
1800         * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
1801         * @version $Rev: 923455 $, $Date: 2010-03-15 22:59:28 +0100 (Mon, 15 Mar 2010) $
1802         */
1803        public static class Extension
1804        {
1805            private boolean isCritical;
1806            private String type;
1807            private String value;
1808    
1809    
1810            /**
1811             * Creates a new instance of Extension.
1812             *
1813             * @param isCritical true for critical extension
1814             * @param type the extension type
1815             * @param value the extension value
1816             */
1817            public Extension( boolean isCritical, String type, String value )
1818            {
1819                super();
1820                this.isCritical = isCritical;
1821                this.type = type;
1822                this.value = value;
1823            }
1824    
1825    
1826            /**
1827             * Checks if is critical.
1828             * 
1829             * @return true, if is critical
1830             */
1831            public boolean isCritical()
1832            {
1833                return isCritical;
1834            }
1835    
1836    
1837            /**
1838             * Sets the critical.
1839             * 
1840             * @param isCritical the new critical
1841             */
1842            public void setCritical( boolean isCritical )
1843            {
1844                this.isCritical = isCritical;
1845            }
1846    
1847    
1848            /**
1849             * Gets the type.
1850             * 
1851             * @return the type
1852             */
1853            public String getType()
1854            {
1855                return type;
1856            }
1857    
1858    
1859            /**
1860             * Sets the type.
1861             * 
1862             * @param type the new type
1863             */
1864            public void setType( String type )
1865            {
1866                this.type = type;
1867            }
1868    
1869    
1870            /**
1871             * Gets the value.
1872             * 
1873             * @return the value
1874             */
1875            public String getValue()
1876            {
1877                return value;
1878            }
1879    
1880    
1881            /**
1882             * Sets the value.
1883             * 
1884             * @param value the new value
1885             */
1886            public void setValue( String value )
1887            {
1888                this.value = value;
1889            }
1890        }
1891    
1892    }