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;
025import java.util.regex.Pattern;
026import java.util.regex.PatternSyntaxException;
027
028import org.apache.directory.api.i18n.I18n;
029import org.apache.directory.api.ldap.model.constants.SchemaConstants;
030import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
031import org.apache.directory.api.util.Strings;
032
033
034/**
035 * A SyntaxChecker which verifies that a value is a facsimile TelephoneNumber according 
036 * to ITU recommendation E.123 for the Telephone number part, and from RFC 4517, par. 
037 * 3.3.11 :
038 * 
039 * <pre>
040 * fax-number       = telephone-number *( DOLLAR fax-parameter )
041 * telephone-number = PrintableString
042 * fax-parameter    = "twoDimensional" |
043 *                    "fineResolution" |
044 *                    "unlimitedLength" |
045 *                    "b4Length" |
046 *                    "a3Width" |
047 *                    "b4Width" |
048 *                    "uncompressed"
049 * </pre>
050 * 
051 * If needed, and to allow more syntaxes, a list of regexps has been added
052 * which can be initialized to other values
053 * 
054 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
055 */
056@SuppressWarnings("serial")
057public final class FacsimileTelephoneNumberSyntaxChecker extends SyntaxChecker
058{
059    /** The default pattern used to check a TelephoneNumber */
060    private static final String DEFAULT_REGEXP = "^ *[+]? *((\\([0-9- ,;/#*]+\\))|[0-9- ,;/#*]+)+$";
061    
062    /** The compiled default pattern */
063    private String defaultRegexp;
064
065    /** The compiled default pattern */
066    private Pattern defaultPattern = Pattern.compile( DEFAULT_REGEXP );
067
068    /** Fax parameters possible values */
069    private static final String TWO_DIMENSIONAL = "twoDimensional";
070    private static final String FINE_RESOLUTION = "fineResolution";
071    private static final String UNLIMITED_LENGTH = "unlimitedLength";
072    private static final String B4_LENGTH = "b4Length";
073    private static final String A3_LENGTH = "a3Width";
074    private static final String B4_WIDTH = "b4Width";
075    private static final String UNCOMPRESSED = "uncompressed";
076
077    /** A set which contains all the possible fax parameters values */
078    private static Set<String> faxParameters = new HashSet<>();
079
080    /** Initialization of the fax parameters set of values */
081    static
082    {
083        faxParameters.add( Strings.toLowerCaseAscii( TWO_DIMENSIONAL ) );
084        faxParameters.add( Strings.toLowerCaseAscii( FINE_RESOLUTION ) );
085        faxParameters.add( Strings.toLowerCaseAscii( UNLIMITED_LENGTH ) );
086        faxParameters.add( Strings.toLowerCaseAscii( B4_LENGTH ) );
087        faxParameters.add( Strings.toLowerCaseAscii( A3_LENGTH ) );
088        faxParameters.add( Strings.toLowerCaseAscii( B4_WIDTH ) );
089        faxParameters.add( Strings.toLowerCaseAscii( UNCOMPRESSED ) );
090    }
091    
092    /**
093     * A static instance of FacsimileTelephoneNumberSyntaxChecker
094     */
095    public static final FacsimileTelephoneNumberSyntaxChecker INSTANCE = 
096        new FacsimileTelephoneNumberSyntaxChecker( SchemaConstants.FACSIMILE_TELEPHONE_NUMBER_SYNTAX );
097    
098    /**
099     * A static Builder for this class
100     */
101    public static final class Builder extends SCBuilder<FacsimileTelephoneNumberSyntaxChecker>
102    {
103        /** The compiled default pattern */
104        private String defaultRegexp;
105
106        /** The compiled default pattern */
107        private Pattern defaultPattern;
108
109        /**
110         * The Builder constructor
111         */
112        private Builder()
113        {
114            super( SchemaConstants.FACSIMILE_TELEPHONE_NUMBER_SYNTAX );
115            setDefaultRegexp( DEFAULT_REGEXP );
116        }
117        
118        
119        /**
120         * Create a new instance of FacsimileTelephoneNumberSyntaxChecker
121         * 
122         * @return A new instance of FacsimileTelephoneNumberSyntaxChecker
123         */
124        @Override
125        public FacsimileTelephoneNumberSyntaxChecker build()
126        {
127            return new FacsimileTelephoneNumberSyntaxChecker( oid, defaultRegexp, defaultPattern );
128        }
129
130
131        /**
132         * Set the default regular expression for the Telephone number
133         * 
134         * @param regexp the default regular expression.
135         * @return The FacsimileTelephneNumber Builder instance
136         */
137        public Builder setDefaultRegexp( String regexp )
138        {
139            defaultRegexp = regexp;
140            
141            try
142            {
143                defaultPattern = Pattern.compile( regexp );
144            }
145            catch ( PatternSyntaxException pse )
146            {
147                // Roll back to the default pattern
148                defaultPattern = Pattern.compile( DEFAULT_REGEXP );
149            }
150
151            return this;
152        }
153    }
154
155    
156    /**
157     * Creates a new instance of TelephoneNumberSyntaxChecker.
158     * 
159     * @param oid the OID
160     */
161    private FacsimileTelephoneNumberSyntaxChecker( String oid )
162    {
163        super( oid );
164    }
165
166    
167    /**
168     * Creates a new instance of TelephoneNumberSyntaxChecker.
169     * 
170     * @param oid The OID
171     * @param defaultRegexp the default regexp to use
172     * @param defaultPattern The default pattern to use
173     */
174    private FacsimileTelephoneNumberSyntaxChecker( String oid, String defaultRegexp, Pattern defaultPattern )
175    {
176        super( oid );
177        
178        this.defaultPattern = defaultPattern;
179        this.defaultRegexp = defaultRegexp;
180    }
181
182    
183    /**
184     * @return An instance of the Builder for this class
185     */
186    public static Builder builder()
187    {
188        return new Builder();
189    }
190
191
192    /**
193     * Get the default regexp (either the original one, or the one that has been set)
194     * 
195     * @return The default regexp
196     */
197    public String getRegexp()
198    {
199        if ( defaultRegexp == null )
200        {
201            return DEFAULT_REGEXP;
202        }
203        else
204        {
205            return defaultRegexp;
206        }
207    }
208
209
210    /**
211     * {@inheritDoc}
212     */
213    @Override
214    public boolean isValidSyntax( Object value )
215    {
216        String strValue;
217
218        if ( value == null )
219        {
220            if ( LOG.isDebugEnabled() )
221            {
222                LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, "null" ) );
223            }
224            
225            return false;
226        }
227
228        if ( value instanceof String )
229        {
230            strValue = ( String ) value;
231        }
232        else if ( value instanceof byte[] )
233        {
234            strValue = Strings.utf8ToString( ( byte[] ) value );
235        }
236        else
237        {
238            strValue = value.toString();
239        }
240
241        if ( strValue.length() == 0 )
242        {
243            if ( LOG.isDebugEnabled() )
244            {
245                LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
246            }
247            
248            return false;
249        }
250
251        // The facsimile telephone number might be composed
252        // of two parts separated by a '$'.
253        int dollarPos = strValue.indexOf( '$' );
254
255        if ( dollarPos == -1 )
256        {
257            // We have no fax-parameter : check the Telephone number
258            boolean result = defaultPattern.matcher( strValue ).matches();
259
260            if ( LOG.isDebugEnabled() )
261            {
262                if ( result )
263                {
264                    LOG.debug( I18n.msg( I18n.MSG_13701_SYNTAX_VALID, value ) );
265                }
266                else
267                {
268                    LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
269                }
270            }
271
272            return result;
273        }
274
275        // First check the telephone number if the '$' is not at the first position
276        if ( dollarPos > 0 )
277        {
278            boolean result = defaultPattern.matcher( strValue.substring( 0, dollarPos - 1 ) ).matches();
279
280            if ( LOG.isDebugEnabled() )
281            {
282                if ( result )
283                {
284                    LOG.debug( I18n.err( I18n.MSG_13701_SYNTAX_VALID, value ) );
285                }
286                else
287                { 
288                    LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
289                    
290                    return false;
291                }
292            }
293
294            // Now, try to validate the fax-parameters : we may
295            // have more than one, so we will store the seen params
296            // in a set to check that we don't have the same param twice
297            Set<String> paramsSeen = new HashSet<>();
298
299            while ( dollarPos > 0 )
300            {
301                String faxParam;
302                int newDollar = strValue.indexOf( '$', dollarPos + 1 );
303
304                if ( newDollar == -1 )
305                {
306                    faxParam = strValue.substring( dollarPos + 1 );
307                }
308                else
309                {
310                    faxParam = strValue.substring( dollarPos + 1, newDollar );
311                }
312
313                if ( faxParam.length() == 0 )
314                {
315                    // Not allowed
316                    if ( LOG.isDebugEnabled() )
317                    {
318                        LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
319                    }
320                    
321                    return false;
322                }
323
324                // Relax a little bit the syntax by lowercasing the param
325                faxParam = Strings.toLowerCaseAscii( faxParam );
326
327                if ( !faxParameters.contains( faxParam ) || paramsSeen.contains( faxParam ) )
328                {
329                    // This parameter is not in the possible set
330                    if ( LOG.isDebugEnabled() )
331                    {
332                        LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
333                    }
334                    
335                    return false;
336                }
337                else
338                {
339                    // It's a correct param, let's add it to the seen 
340                    // params.
341                    paramsSeen.add( faxParam );
342                }
343
344                dollarPos = newDollar;
345            }
346
347            if ( LOG.isDebugEnabled() )
348            {
349                LOG.debug( I18n.msg( I18n.MSG_13701_SYNTAX_VALID, value ) );
350            }
351            
352            return true;
353        }
354
355        // We must have a valid telephone number !
356        if ( LOG.isDebugEnabled() )
357        {
358            LOG.debug( I18n.err( I18n.ERR_13210_SYNTAX_INVALID, value ) );
359        }
360        
361        return false;
362    }
363}