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 default pattern */ 063 private final String defaultRegexp; 064 065 /** The compiled default pattern */ 066 private Pattern defaultPattern; 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 * @return A new instance of FacsimileTelephoneNumberSyntaxChecker 122 */ 123 @Override 124 public FacsimileTelephoneNumberSyntaxChecker build() 125 { 126 return new FacsimileTelephoneNumberSyntaxChecker( oid, defaultRegexp, defaultPattern ); 127 } 128 129 130 /** 131 * Set the default regular expression for the Telephone number 132 * 133 * @param regexp the default regular expression. 134 */ 135 public Builder setDefaultRegexp( String regexp ) 136 { 137 defaultRegexp = regexp; 138 139 try 140 { 141 defaultPattern = Pattern.compile( regexp ); 142 } 143 catch ( PatternSyntaxException pse ) 144 { 145 // Roll back to the default pattern 146 defaultPattern = Pattern.compile( DEFAULT_REGEXP ); 147 } 148 149 return this; 150 } 151 } 152 153 154 /** 155 * Creates a new instance of TelephoneNumberSyntaxChecker. 156 */ 157 private FacsimileTelephoneNumberSyntaxChecker( String oid ) 158 { 159 this( oid, DEFAULT_REGEXP, Pattern.compile( DEFAULT_REGEXP ) ); 160 } 161 162 163 /** 164 * Creates a new instance of TelephoneNumberSyntaxChecker. 165 */ 166 private FacsimileTelephoneNumberSyntaxChecker( String oid, String defaultRegexp, Pattern defaultPattern ) 167 { 168 super( oid ); 169 170 this.defaultPattern = defaultPattern; 171 this.defaultRegexp = defaultRegexp; 172 } 173 174 175 /** 176 * @return An instance of the Builder for this class 177 */ 178 public static Builder builder() 179 { 180 return new Builder(); 181 } 182 183 184 /** 185 * Get the default regexp (either the original one, or the one that has been set) 186 * 187 * @return The default regexp 188 */ 189 public String getRegexp() 190 { 191 if ( defaultRegexp == null ) 192 { 193 return DEFAULT_REGEXP; 194 } 195 else 196 { 197 return defaultRegexp; 198 } 199 } 200 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override 206 public boolean isValidSyntax( Object value ) 207 { 208 String strValue; 209 210 if ( value == null ) 211 { 212 if ( LOG.isDebugEnabled() ) 213 { 214 LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, "null" ) ); 215 } 216 217 return false; 218 } 219 220 if ( value instanceof String ) 221 { 222 strValue = ( String ) value; 223 } 224 else if ( value instanceof byte[] ) 225 { 226 strValue = Strings.utf8ToString( ( byte[] ) value ); 227 } 228 else 229 { 230 strValue = value.toString(); 231 } 232 233 if ( strValue.length() == 0 ) 234 { 235 if ( LOG.isDebugEnabled() ) 236 { 237 LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) ); 238 } 239 240 return false; 241 } 242 243 // The facsimile telephone number might be composed 244 // of two parts separated by a '$'. 245 int dollarPos = strValue.indexOf( '$' ); 246 247 if ( dollarPos == -1 ) 248 { 249 // We have no fax-parameter : check the Telephone number 250 boolean result = defaultPattern.matcher( strValue ).matches(); 251 252 if ( LOG.isDebugEnabled() ) 253 { 254 if ( result ) 255 { 256 LOG.debug( I18n.msg( I18n.MSG_04489_SYNTAX_VALID, value ) ); 257 } 258 else 259 { 260 LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) ); 261 } 262 } 263 264 return result; 265 } 266 267 // First check the telephone number if the '$' is not at the first position 268 if ( dollarPos > 0 ) 269 { 270 boolean result = defaultPattern.matcher( strValue.substring( 0, dollarPos - 1 ) ).matches(); 271 272 if ( LOG.isDebugEnabled() ) 273 { 274 if ( result ) 275 { 276 LOG.debug( I18n.msg( I18n.MSG_04489_SYNTAX_VALID, value ) ); 277 } 278 else 279 { 280 LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) ); 281 282 return false; 283 } 284 } 285 286 // Now, try to validate the fax-parameters : we may 287 // have more than one, so we will store the seen params 288 // in a set to check that we don't have the same param twice 289 Set<String> paramsSeen = new HashSet<>(); 290 291 while ( dollarPos > 0 ) 292 { 293 String faxParam; 294 int newDollar = strValue.indexOf( '$', dollarPos + 1 ); 295 296 if ( newDollar == -1 ) 297 { 298 faxParam = strValue.substring( dollarPos + 1 ); 299 } 300 else 301 { 302 faxParam = strValue.substring( dollarPos + 1, newDollar ); 303 } 304 305 if ( faxParam.length() == 0 ) 306 { 307 // Not allowed 308 if ( LOG.isDebugEnabled() ) 309 { 310 LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) ); 311 } 312 313 return false; 314 } 315 316 // Relax a little bit the syntax by lowercasing the param 317 faxParam = Strings.toLowerCaseAscii( faxParam ); 318 319 if ( !faxParameters.contains( faxParam ) || paramsSeen.contains( faxParam ) ) 320 { 321 // This parameter is not in the possible set 322 if ( LOG.isDebugEnabled() ) 323 { 324 LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) ); 325 } 326 327 return false; 328 } 329 else 330 { 331 // It's a correct param, let's add it to the seen 332 // params. 333 paramsSeen.add( faxParam ); 334 } 335 336 dollarPos = newDollar; 337 } 338 339 if ( LOG.isDebugEnabled() ) 340 { 341 LOG.debug( I18n.msg( I18n.MSG_04489_SYNTAX_VALID, value ) ); 342 } 343 344 return true; 345 } 346 347 // We must have a valid telephone number ! 348 if ( LOG.isDebugEnabled() ) 349 { 350 LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) ); 351 } 352 353 return false; 354 } 355}