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 */
020package org.apache.directory.api.ldap.model.schema.syntaxCheckers;
021
022
023import java.util.HashSet;
024import java.util.Set;
025
026import org.apache.directory.api.i18n.I18n;
027import org.apache.directory.api.ldap.model.constants.SchemaConstants;
028import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
029import org.apache.directory.api.util.Chars;
030import org.apache.directory.api.util.Strings;
031
032
033/**
034 * A SyntaxChecker which verifies that a value is a DSEType according to 
035 * http://tools.ietf.org/id/draft-ietf-asid-ldapv3-attributes-03.txt, par 6.2.1.5 :
036 * <pre>
037 * &lt;DSEType&gt;    ::= '(' &lt;sp&gt;* &lt;DSEBit&gt; &lt;sp&gt;* &lt;DSEBitList&gt; ')'
038 * &lt;DSEBitList&gt; ::= '$' &lt;sp&gt;* &lt;DSEBit&gt; &lt;sp&gt;* &lt;DSEBitList&gt; | e      
039 * &lt;DSEBit&gt;     ::= 'root' | 'glue' | 'cp' | 'entry' | 'alias' | 'subr' |
040 *                  'nssr' | 'supr' | 'xr' | 'admPoint' | 'subentry' |
041 *                  'shadow' | 'zombie' | 'immSupr' | 'rhob' | 'sa'
042 * </pre>
043 *
044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045 */
046@SuppressWarnings("serial")
047public final class DseTypeSyntaxChecker extends SyntaxChecker
048{
049    /** The DSE BITS keywords */
050    private static final String[] DSE_BITS_STRINGS =
051        {
052            "root", "glue", "cp", "entry", "alias", "subr",
053            "nssr", "supr", "xr", "admPoint", "subentry",
054            "shadow", "zombie", "immSupr", "rhob", "sa"
055    };
056
057    /** The Set which contains the DESBits */
058    private static final Set<String> DSE_BITS = new HashSet<>();
059    
060    /** Initialization of the country set */
061    static
062    {
063        for ( String country : DSE_BITS_STRINGS )
064        {
065            DSE_BITS.add( country );
066        }
067    }
068
069    /**
070     * A static instance of DseTypeSyntaxChecker
071     */
072    public static final DseTypeSyntaxChecker INSTANCE = new DseTypeSyntaxChecker( SchemaConstants.DSE_TYPE_SYNTAX );
073    
074    /**
075     * A static Builder for this class
076     */
077    public static final class Builder extends SCBuilder<DseTypeSyntaxChecker>
078    {
079        /**
080         * The Builder constructor
081         */
082        private Builder()
083        {
084            super( SchemaConstants.DSE_TYPE_SYNTAX );
085        }
086        
087        
088        /**
089         * Create a new instance of DseTypeSyntaxChecker
090         * @return A new instance of DseTypeSyntaxChecker
091         */
092        @Override
093        public DseTypeSyntaxChecker build()
094        {
095            return new DseTypeSyntaxChecker( oid );
096        }
097    }
098
099    
100    /** Initialization of the country set */
101    static
102    {
103        for ( String country : DSE_BITS_STRINGS )
104        {
105            DSE_BITS.add( country );
106        }
107    }
108
109
110    /**
111     * Creates a new instance of DSETypeSyntaxChecker.
112     *
113     * @param oid The OID to use for this SyntaxChecker
114     */
115    private DseTypeSyntaxChecker( String oid )
116    {
117        super( oid );
118    }
119
120    
121    /**
122     * @return An instance of the Builder for this class
123     */
124    public static Builder builder()
125    {
126        return new Builder();
127    }
128
129
130    /**
131     * {@inheritDoc}
132     */
133    @Override
134    public boolean isValidSyntax( Object value )
135    {
136        String strValue;
137
138        if ( value == null )
139        {
140            if ( LOG.isDebugEnabled() )
141            {
142                LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, "null" ) );
143            }
144            
145            return false;
146        }
147
148        if ( value instanceof String )
149        {
150            strValue = ( String ) value;
151        }
152        else if ( value instanceof byte[] )
153        {
154            strValue = Strings.utf8ToString( ( byte[] ) value );
155        }
156        else
157        {
158            strValue = value.toString();
159        }
160
161        // We must have at least '(cp)', '(xr)' or '(ca)'
162        if ( strValue.length() < 4 )
163        {
164            if ( LOG.isDebugEnabled() )
165            {
166                LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
167            }
168            
169            return false;
170        }
171
172        // Check the opening and closing parenthesis
173        if ( ( strValue.charAt( 0 ) != '(' )
174            || ( strValue.charAt( strValue.length() - 1 ) != ')' ) )
175        {
176            if ( LOG.isDebugEnabled() )
177            {
178                LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
179            }
180            
181            return false;
182        }
183
184        Set<String> keywords = new HashSet<>();
185        int len = strValue.length() - 1;
186        boolean needKeyword = true;
187
188        // 
189        for ( int i = 1; i < len; /* */)
190        {
191            // Skip spaces
192            while ( ( i < len ) && ( strValue.charAt( i ) == ' ' ) )
193            {
194                i++;
195            }
196
197            int pos = i;
198
199            // Search for a keyword
200            while ( ( i < len ) && Chars.isAlphaASCII( strValue, pos ) )
201            {
202                pos++;
203            }
204
205            if ( pos == i )
206            {
207                // No keyword : error
208                if ( LOG.isDebugEnabled() )
209                {
210                    LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
211                }
212                
213                return false;
214            }
215
216            String keyword = strValue.substring( i, pos );
217            i = pos;
218
219            if ( !DSE_BITS.contains( keyword ) )
220            {
221                // Unknown keyword
222                if ( LOG.isDebugEnabled() )
223                {
224                    LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
225                }
226                
227                return false;
228            }
229
230            // Check that the keyword has not been met
231            if ( keywords.contains( keyword ) )
232            {
233                if ( LOG.isDebugEnabled() )
234                {
235                    LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
236                }
237                
238                return false;
239            }
240
241            keywords.add( keyword );
242            needKeyword = false;
243
244            // Skip spaces
245            while ( ( i < len ) && ( strValue.charAt( i ) == ' ' ) )
246            {
247                i++;
248            }
249
250            // Do we have another keyword ?
251            if ( ( i < len ) && ( strValue.charAt( i ) == '$' ) )
252            {
253                // yes
254                i++;
255                needKeyword = true;
256            }
257        }
258
259        // We are done
260        if ( LOG.isDebugEnabled() )
261        {
262            if ( needKeyword )
263            {
264                LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
265            }
266            else
267            {
268                LOG.debug( I18n.msg( I18n.MSG_13701_SYNTAX_VALID, value ) );
269            }
270        }
271
272        return !needKeyword;
273    }
274}