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